class MyPromise {
constructor(executor) {
// 関数以外の引数はエラーを出す
if (!(executor instanceof Function)) {
throw new Error("Require a function to init MyPromise");
}
// 初期ステートをpending、値をundefinedに
this.state = "pending";
this.value = undefined;
this.onFulfilledQueue = [];
this.onRejectedQueue = [];
// executor関数を実行、ここでコンテキストのthisをプロミスインスタンスにバインドする
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled =
onFulfilled instanceof Function ? onFulfilled : (value) => value;
onRejected =
onRejected instanceof Function
? onRejected
: (value) => {
throw value;
};
const { value, state } = this;
return new MyPromise((resolveNext, rejectNext) => {
const onFulfilledFn = () => {
try {
const res = onFulfilled(value);
this._resolvePromise(res, resolveNext, rejectNext);
} catch (e) {
rejectNext(e);
}
};
const onRejectedFn = () => {
try {
const res = onRejected(value);
this._resolvePromise(res, resolveNext, rejectNext);
} catch (e) {
rejectNext(e);
}
};
switch (state) {
case "fulfilled":
onFulfilledFn(value);
break;
case "rejected":
onRejectedFn(value);
break;
case "pending":
//ここはエラー処理付きのバージョンをプッシュ
this.onFulfilledQueue.push(onFulfilledFn);
this.onRejectedQueue.push(onRejectedFn);
break;
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callback) {
return this.then(
(value) => MyPromise.resolve(callback()).then(() => value),
(err) =>
MyPromise.resolve(callback()).then(() => {
throw err;
})
);
}
static resolve(value) {
return new MyPromise((resolve) => {
resolve(value);
});
}
static reject(value) {
return new MyPromise((resolve, reject) => {
reject(value);
});
}
_resolvePromise(res, resolve, reject) {
if (res instanceof MyPromise) {
res.then(resolve, reject);
} else {
resolve(res);
}
}
resolve(val) {
if (this.state === "pending") {
this.state = "fulfilled";
this.value = val;
// 配列に未実行の関数が存在する場合、先頭から取り出して実行
while (this.onFulfilledQueue.length) {
this.onFulfilledQueue.shift()(val);
}
}
}
reject(val) {
if (this.state === "pending") {
this.state = "rejected";
this.value = val;
while (this.onRejectedQueue.length) {
this.onRejectedQueue.shift()(val);
}
}
}
static race(promises) {
return new MyPromise((resolve, reject) => {
for (let [idx, promise] of promises.entries()) {
// もしプロミスインスタンスではない場合、一回プロミスに変換する
if (!(promise instanceof MyPromise)) {
promise = MyPromise.resolve(promise);
}
// 次にthenを呼び出す
promise.then(
(res) => {
resolve(res);
},
(err) => {
reject(err);
}
);
}
});
}
static all(promises) {
return new MyPromise((resolve, reject) => {
let results = [];
let count = 0;
for (let [idx, promise] of promises.entries()) {
// もしプロミスインスタンスではない場合、一回プロミスに変換する
if (!(promise instanceof MyPromise)) {
promise = MyPromise.resolve(promise);
}
// 次にthenを呼び出す
promise.then(
(res) => {
// 結果はプロミスの投入順番と対応するためindex必須
results[idx] = res;
count++;
// すべてのプロミスがfulfilledになれば、結果配列をリターン
if (count === promises.length) {
resolve(results);
}
},
(err) => {
// どれか一つのプロミスが失敗したら、全体も失敗
reject(err);
}
);
}
});
}
}
console.log("create new promise");
let p = new MyPromise((resolve, reject) => {
console.log("constructor callback");
resolve("promise value");
});
p.then((res) => {
console.log(res); // promise value
})
.then((res) => {
console.log(res); // undefined
return 123;
})
.then((res) => {
console.log(res); // 123
return new MyPromise((resolve) => {
resolve(456);
});
})
.then((res) => {
console.log(res); // 456
return "return value";
})
.then()
.then("string")
.then((res) => {
console.log(res); // return value
throw new Error("error occurred!");
})
.then((res) => {
console.log(res); // 出力なし
})
.catch((err) => {
console.error(err.message); // error occurred!
return "error!";
})
.then((res) => {
console.log("after catch " + res); // after catch error!
return 789;
})
.finally((res) => {
console.log(res); // undefined
console.log("done!"); // done!
});
console.log("chain ends");
MyPromise.resolve(100).then(console.log);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.