blog

プロミスのJS実装

JS - PromisesA+ 仕様書.md コード // 中間プロミスの解析 const = => { // 中間プロミスのパース...

Dec 13, 2020 · 5 min. read

    コード

    // 中間プロミスを解析する
    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)
    
    Read next

    npm / yarn共通コマンド

    1. npm initが自動的にファイルを生成する 2. 指定したモジュールをインストールする 以下のコマンドを実行すると、ダウンロードしたパッケージの詳細を保存するpackage-lock.jsonファイルが自動的に生成されます。 yarnはまた、ダウンロードしたパッケージをキャッシュし、リソースの利用を最大化するために並列ダウンロードを使用するパッケージマネージャでもあるので、インストールはより高速になります...

    Dec 13, 2020 · 2 min read

    J14 2つの値を比較する

    Dec 12, 2020 · 1 min read

    JavaScriptにSymbol型がある理由

    Dec 10, 2020 · 2 min read

    [DFS] 近接数の最長経路

    Dec 9, 2020 · 2 min read