JSでPromiseクラスを実現

8435 ワード

Promise
  • は、実はクラスであり、内部にはレギュレータが保存されており、レシオとレギュレータを露出することによって、対応するレギュレータ
  • がトリガされる.
  • 内部には3つの状態値、pending、fulfiled、failed
  • があります.
  • Promiseは、マイクロタスクであり、settimoutなどのマクロタスクとは異なり、実行順序は、まずメインスレッドであり、その後、マイクロタスクであり、マクロタスク
  • である.
  • には以下の方法があります.then、catch、all、race、reolve、reject
  • 一旦Promiseを実施したら、必ず実行します.
  • を停止できません.
  • Promiseでは、エラーが発生してもrejectが起動されますので、プログラムを中断することはありません.
  • catchは実はthenのシンタックスキャンディーで、then(null,rej)に相当しています.つまり、空
  • に反転しました.
  • もしthenにフィードバックが入っていないなら、promiseのresoveまたはrejectの値は次のthenまたはcatchに伝えられます.
    const p = new Promise((res) => res(2));
    p.then(null).then((data) => console.log(data)); // 2
    p.then(() => {}).then((data) => console.log(data)); // undefined
    p.catch((data) => {console.log('catch', data)}).then((data) => console.log('then', data)); // then, 2
    実現する
    内部変数
    function myPromise(func) {
        this.status = 'pending'; //   
        this.value = undefined; //  
        this.resolveList = []; //     
        this.rejectList = []; //     
    }
    内部reolveとrejectの方法
  • は、入ってきたfunc
  • を同時に実行する.
    function myPromise(func) {
        this.status = 'pending'; //   
        this.value = undefined; //  
        this.resolveList = []; //     
        this.rejectList = []; //     
        
        const resolve = (data) => {
            if (this.status === 'pending') {
                this.status = 'fulfiled';
                this.value = data;
                this.resolveList.forEach((resFunc) => resFunc(data));
                this.resolveList.length = 0;
            }
        };
        
        const reject = (data) => {
            if (this.status === 'pending') {
                this.status = 'failed';
                this.value = data;
                this.rejectList.forEach((resFunc) => resFunc(data));
                this.rejectList.length = 0;
            }
        };
        
        if (typeof func === 'function') {
            try {
                func(resolve, reject); // promise   func      ,   resolve reject      
            } catch(err) {
                reject(err);
            }
        }
    }
    thenメソッド
  • then方法は、プロトタイプチェーン上で定義され、プロミセ
  • に戻る.
  • 分の3つの状態で処理し、pendingの場合はコールバックキューにのみフィードバックを追加し、同時にpromise化、fulfilledまたはfailedの場合は直接実行し、promise
  • に戻ります.
    function handleResolve(res, rej, resCb, rejCb) {
        queueMicrotask(() => {
            //         ,        
            try {
                const ret = typeof resCb === 'function' ? resCb(this.value) : this.value;
                if (ret instanceof myPromise) {
                    ret.then(res, rej);
                } else {
                    res(ret);
                }   
            } catch (err) {
                rej(err);
            }
        });
    }
    
    function handleReject(res, rej, resCb, rejCb) {
        queueMicrotask(() => {
            //         ,        
            try {
                const ret = typeof rejCb === 'function' ? rejCb(this.value) : this.value;
                if (ret instanceof myPromise) {
                    ret.then(res, rej);
                } else {
                    rej(ret);
                }   
            } catch (err) {
                rej(err);
            }
        });
    }
    
    myPromise.prototype.then = function (resCb, rejCb) {
        if (this.status === 'pending') {
            return new myPromise((res, rej) => {
                this.resolveList.push(() => {
                    handleResolve(res, rej, resCb, rejCb));
                });
                this.rejectList.push(() => {
                    handleReject(res, rej, resCb, rejCb));
                });
            });
        } else if (this.status === 'fulfiled') {
            return new myPromise((res, rej) => {
                handleResolve(res, rej, resCb, rejCb));
            });
        } else if (this.status === 'failed') {
            return new myPromise((res, rej) => {
                handleReject(res, rej, resCb, rejCb));
            });
        }
    };
  • ここで、もしrecbやrejCbが空に入ったら、promiseのvalueを返してくれるpromise
  • を見ました.
  • 、cbがpromiseに戻ると、そのpromiseのresまたはrejで上位層のresまたはrejがトリガされ、同時にcbのdata値は内部層promiseのvalue
  • である.
  • 例は以下の通りです.
  • const p1 = new Promise((res) => res(1));
    p1.then(null).then((data) => console.log(data));
    p1.then(() => {}).then((data) => console.log(data));
    
    const p2 = new Promise((res, rej) => rej(2));
    p2.then(() => {}).catch((err) => console.log(err));
    p2.then(() => {}, () => {}).catch((err) => console.log(err));
    
    /*
        :
    1
    undefined
    2
    undefined
    */
  • は、プロミセのresoveまたはrejectがthenに対応するコールバック関数を入力しないと、そのvalueを継続的に伝達していきます.
    タスク作成タイミング
  • は、reloveおよびrejectの定義から見られますが、プロミスの状態とそのvalueだけを変えると、マイクロタスク、すなわち、非同期はどこで作成されますか?注意次の例は、
  • です.
    const p = new Promise((res) => res(1));
    p.then(() => console.log('p1 then'))
     .then(() => console.log('p1 then then'));
    const p2 = new Promise((res) => res(2));
    p2.then(() => console.log('p2 then'));
    
    /*
        :p1 then -> p2 then -> p1 then then
    */
  • マイクロタスクは、thenにおいて作成され、then方法の定義に戻ると、handleResoliveおよびhandleRejectが見られ、queue Microtaskを利用してマイクロタスクを作成する
  • が見られます.
  • 控訴例及びthen方法を組み合わせて見ることができます.
  • p 1.thenがコールバック関数を登録し、彼をマイクロタスクに置いた後、promise
  • に戻りました.
  • が返されるpromiseのresoveは、現在実行されていないthenがpromiseに戻るというマイクロタスクに置かれています.
  • ですので、戻りプロミスでthenに登録されたコールバック関数は、resoloveListに入れられています.つまり、現在は
  • を実行していません.
  • は次にp 2.thenに行き、また、1つのマイクロタスク
  • を生成する.
  • 現在のマイクロタスクのキューには、p 1 thenの出力を先に実行し、p 1.thenを実行してpromiseのResoliveに戻り、また新たにマイクロタスクを作成し、マイクロタスクのキュー
  • に置く.
  • は次にp 2 then、p 1 then
  • を実行する.
    catchの方法
  • catch方法、すなわちthenのシンタックス糖
  • myPromise.prototype.catch = (rej) => myPromise.prototype.then(null, rej);
    allメソッド
  • all方法は、一つの配列に入って、一つのpromiseを出力し、全部のresoveを出力する時、出力promise状態がfulfiledになり、resove
  • をトリガします.
  • いずれかが入ってきたプロモーションに失敗した場合、プロモーション状態がfailedとなり、rejectをトリガしますが、他のプロモーションの継続実行は中止されません.他のプロモーションの実行はpromise.allがpromiseに戻る状態には影響しません.
    myPromise.prototype.all = (iterable) => {
        const len = iterable.length;
        const result = [];
        return new myPromise((resolve, reject) => {
            let index = 0;
            iterable.forEach((item) => {
                item.then((data) => {
                    result.push(data);
                    index++;
                    if (index === len) resolve(result);
                }, (err) => {
                    reject(err);
                });
            });
        });
    }
    raceメソッド
  • race方法はpromise配列の中で最初の実行が完了した状態に戻り、fulfiledであれfailedであれ、配列中の残りのpromiseに影響を与えずに
  • を実行し続ける.
    myPromise.prototype.race = (iterable) => {
        let done = false;
        return new myPromise((resolve, reject) => {
            iterable.forEach((item) => {
                item.then((data) => {
                    if (!done) {
                        done = true;
                        resolve(data);
                    }
                }, (err) => {
                    if (!done) {
                        done = true;
                        reject(err);
                    }
                });
            });
        });
    }
  • 上記のdoneフラグは取り除かれます.promiseでは、resoveは2回しか実行されません.最初にthen関数を実行しただけです.具体的な理由は、第1回のresoveでは、resove Listのコールバック関数を実行した後、それをクリアします.即ち、実行が完了してから新たなthenメソッドを登録しないと、第2回のresoliveはフィードバックをトリガしません.
  • また、第二回のresolove後に新たに登録されたthenであっても、その取得値は、第二回のresoveの値ではなく、プロミゼ内のvalueを更新すると、プロミセ状態からfulfiledまたはfailedに変化するときにのみ発生します.promiseの状態が固まります.例は以下の通りです.
    const p1 = new Promise((res) => {
        setTimeout(() => { console.log('res(1)'); res(1)}, 1000);
        setTimeout(() => { console.log('res(2)'); res(2)}, 3000);
    });
    
    p1.then((data) => console.log(data));
    
    setTimeout(() => {
        console.log('then again');
        p1.then((data) => console.log(data));
    }, 5000);
    
    /* 
       res(1) -> 1  -> res(2) -> then again -> 1
        1   2
    */
  • はraceに戻り、上記のrace方法は以下のように簡略化されています.
    myPromise.prototype.race = (iterable) => {
        return new myPromise((resolve, reject) => {
            iterable.forEach((item) => {
                item.then(resolve, reject);
            });
        });
    }
    外部reolveとrejectの方法
  • reeveとrejectの方法は、内部定義を暴露するreloveとrejectで、promise
  • に戻ります.
    myPromise.prototype.reslove = (data) => {
        return new myPromise((resolve, reject) => {
            resolve(data);
        });
    };
    
    myPromise.prototype.reject = (data) => {
        return new myPromise((resolve, reject) => {
            reject(data);
        });
    };