コード
// 中間プロミスを解析する
const resolvePromise = (x, promise2, resolve, reject) => {
// Promiseを循環参照できない
if (x === promise2) {
const err = new TypeError("約束を循環参照できない!");
return reject(err);
}
// もしxがオブジェクトか関数なら、さらに次の判定を行う。,
// そうでなければ、直接解決する
if (x != null && (typeof x === "object" || typeof x === "function")) {
// resolveまたはrejectが呼び出されたかどうかをマークするために使われるcalledを定義する。
let called = false;
// を呼び出してみる。
try {
// thenはゲッターかもしれないので、直接xを呼び出すことはできない。.then,
const then = x.then;
// 他のPromise実装との互換性のため、xに直接instanofを使うことはできない。
// xがthenableオブジェクトである限り、そうでなければ、単にresolveする。
if (typeof then === "function") {
then.call(
x,
(y) => {
if (called) return;
called = true;
// 再帰的構文解析
resolvePromise(y, promise2, resolve, reject);
},
(reason) => {
if (called) return;
called = true;
reject(reason);
}
);
} else {
resolve(x);
}
} catch (err) {
// もし途中で何か問題が発生したら、REJECTする。
if (called) return;
called = true;
reject(err);
}
} else {
resolve(x);
}
};
// 状態を列挙する
const STATUS = {
PENDING: Symbol("PENDING"),
RESOLVED: Symbol("FULFILLED"),
REJECTED: Symbol("REJECTED"),
};
// MyPromise
class MyPromise {
// コンストラクタ
constructor(executor) {
// 状態をPendingに初期化する
this.status = STATUS.PENDING;
// 値と理由を初期化する
this.value = undefined;
this.reason = undefined;
// コールバック・キューを初期化する
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
// resolveを実装する
const resolve = (value) => {
// ステータスが保留中でなければ、return
if (this.status !== STATUS.PENDING) return;
// 状態をfullfilledに変更する
this.status = STATUS.RESOLVED;
// 値を設定する
this.value = value;
// resolve後にコールバック関数を実行する
this.onResolvedCallbacks.forEach((callback) => callback());
};
// resolveと同じようにrejectを実装する
const reject = (reason) => {
if (this.status !== STATUS.PENDING) return;
this.status = STATUS.REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach((callback) => callback());
};
//
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// MyPromise.prototype.then
then(onResolved, onRejected) {
// 値のパススルーを実装する
if (typeof onResolved !== "function")
onResolved = (value) => {
return value;
};
if (typeof onRejected !== "function")
onRejected = (reason) => {
throw reason;
};
// 新しいプロミスを作成する
const promise2 = new MyPromise((resolve, reject) => {
// 状態を区別する
switch (this.status) {
// promise保留状態の場合、結果はまだ確定できない。,
// まず、resolveとrejectのコールバック関数をコールバック配列に追加する。
case STATUS.PENDING:
this.onResolvedCallbacks.push(() => {
// 非同期をシミュレートする
setTimeout(() => {
try {
// thenの戻り値を取得する
const x = onResolved(this.value);
// 戻り値を解析する
resolvePromise(x, promise2, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
break;
// promisefullfilledの状態になったら、onResolvedを直接実行する。
case STATUS.RESOLVED:
setTimeout(() => {
try {
const x = onResolved(this.value);
resolvePromise(x, promise2, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
break;
// promise拒否状態になったら、onRejectedを直接実行する。
case STATUS.REJECTED:
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
break;
}
});
// この約束を返す
return promise2;
}
// MyPromise.prototype.catch
catch(onRejected) {
return this.then(null, onRejected);
}
// MyPromise.prototype.finally
finally(onFinally) {
return this.then(onFinally, onFinally);
}
// MyPromise.all
static all(promises) {
let resolvedCount = 0;
const length = promises.length,
result = new Array(length),
resolvedCountAdd = (index, value, resolve) => {
resolvedCount++;
result[index] = value;
if (resolvedCount === length) {
resolve(result);
}
};
return new MyPromise((resolve, reject) => {
for (let i = 0; i < length; i++) {
promises[i].then(
(value) => {
resolvedCountAdd(i, value, resolve);
},
(reason) => {
reject(reason);
}
);
}
});
}
// MyPromise.any
static any(promises) {
let rejectedCount = 0;
const length = promises.length,
result = new Array(length),
rejectedCountAdd = (index, reason, reject) => {
rejectedCount++;
result[index] = reason;
if (rejectedCount === length) {
reject(result);
}
};
return new MyPromise((resolve, reject) => {
for (let i = 0; i < length; i++) {
promises[i].then(
(value) => {
resolve(value);
},
(reason) => {
rejectedCountAdd(i, reason, reject);
}
);
}
});
}
// MyPromise.allSettled
static allSettled(promises) {
let processedCount = 0;
const length = promises.length,
result = new Array(length),
processedCountAdd = (index, data, resolve) => {
processedCount++;
result[index] = data;
if (processedCount === length) {
resolve(result);
}
};
return new MyPromise((resolve) => {
for (let i = 0; i < length; i++) {
promises[i].then(
(value) => {
processedCountAdd(i, value, resolve);
},
(reason) => {
processedCountAdd(i, reason, resolve);
}
);
}
});
}
// MyPromise.race
static race(promises) {
const length = promises.length;
return new MyPromise((resolve, reject) => {
for (let i = 0; i < length; i++) {
promises[i].then(
(value) => {
resolve(value);
},
(reason) => {
reject(reason);
}
);
}
});
}
// MyPromise.resolve
static resolve(value = undefined) {
// オブジェクトでない場合は、resolveを直接返す。
if (!(value instanceof Object)) {
return new MyPromise((resolve) => {
resolve(value);
});
}
// プロミスのインスタンスであれば、そのまま返す
if (value instanceof MyPromise) {
return value;
}
// もしそれがthenableオブジェクトであれば、それをラップして返す。,
// そうでなければ、resolveを直接返す
const then = value.then;
if (typeof then === "function") {
return new Promise(then.bind(value));
} else {
return new MyPromise((resolve) => {
resolve(value);
});
}
}
// MyPromise.reject
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
// promises-aplus-testsでテストする
static deferred() {
const defer = {};
defer.promise = new MyPromise((resolve, reject) => {
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
}
}
module.exports = MyPromise;
バリデート
MyPromise.jsを新規作成し、上記のコードをコピーして、以下のコマンドを実行します:
npm i promises-aplus-tests -g
promises-aplus-tests MyPromise.js
# ...
# 872 passing (18s)