JavaScriptはどうして自分でPromiseを書きますか?


この文章をよく読んでください.簡単で機能的なPromiseを自分でカプセル化して、Promiseに対する理解を深めることができます.
提案:この文章を読む前に、あなたにお願いします.
  • ES 6の文法を知っています.
  • Promises/A+仕様を知る[Promises/A+]
  • はPromise
  • を使用します.
    文章は長くて、コードの一貫性が強くて、簡単から始めて、読者は必要に応じて選んで読むことができます.
    一.一番簡単なPromise
    class Promise {
        constructor (executor) {
            if (typeof executor !== 'function') 
                throw new TypeError(`Promise resolver ${executor} is not a function`) 
            
            /*      */
            this.state = 'pending'
            this.value = undefined
            this.reason = undefined
            
            /* 
                     resolve, reject
                1.pending -> fulfilled, pending -> rejected 
                2.      Promise    this.value = value, this.reason = reason
            */
            const resolve = value => {
                if (this.state === 'pending') {
                    this.state = 'fulfilled'
                    this.value = value
                }
            }
            const reject = reason => {
                if (this.state === 'pending') {
                    this.state = 'rejected'
                    this.reason = reason
                }
            }
            
            executor(resolve, reject)
        }
        
        then (onFulfilled, onRejected) {
            if (this.state === 'fulfilled') {
                onFulfilled(this.value)
            }
            if (this.state === 'rejected') {
                onRejected(this.reason)
            }
        }
    }
    
    ps:テストツールはvsCodeのQuokkaプラグインです.Promiseの状態関数resおよびrejによれば、thenの処理関数onFulfilledおよびonRejectedを実行することに対応する.
    二.非同期のPromise
    1.then()は非同期である
    Promiseの中のthen関数のコードは非同期で実行されていることを知っていますが、私達が書いたのはこれではなく、検証できます.
    明らかにこのコードは同期して実行されますが、私たちが欲しい出力順序は0 2 1です.だから、setTimeoutを使ってこの非同期ステップをシミュレートすることができます.
    class Promise {
        constructor (executor) { ... }
        
        then (onFulfilled, onRejected) {
            if (this.state === 'fulfilled') {
            
                /*   setTimeout     */
                setTimeout(() => {
                    onFulfilled(this.value)
                }, 0);
                
            }
            if (this.state === 'rejected') {
                setTimeout(() => {
                    onRejected(this.reason)
                }, 0);
            }
        }
    }
    
    OK、完璧に欲しいものを手に入れます.
    2.状態関数非同期実行
    状態関数res/rejは非同期のために実行される時、私達は見ることができます.thenは無反応です. なぜですかthen関数が実行されると、resは非同期で実行されるため、状態はまだpendingです.then関数の中にはまだ状態がpendingの処理がありません.コードを修正してください.
    class Promise {
        constructor (executor) { 
            ...
            /*          ,           */
            this.resolveCallBackList = []
            this.rejectCallBackList = []
        
            const resolve = value => {
                if (this.state === 'pending') {
                    ...
                    /*    ,               */
                    this.resolveCallBackList.length > 0 
                    && this.resolveCallBackList.forEach(e => e())
                }
            }
            const reject = reason => {
                if (this.state === 'pending') {
                    ...
                    this.rejectCallBackList.length > 0 
                    && this.rejectCallBackList.forEach(e => e())
                }
            }
            ...
        }
        
        then (onFulfilled, onRejected) {
            ...
            
            /*    pending ,               */
            if (this.state === 'pending') {
                onFulfilled && this.resolveCallBackList.push( () => {
                    onFulfilled(this.value)
                })
                onRejected && this.rejectCallBackList.push( () => {
                    onRejected(this.reason)
                })
            }
        }
    }
    
    このように、状態関数が非同期で実行される場合にも処理が可能であり、状態がpendingである場合には、処理関数onFulfilled/onRejectedを保存し、状態関数res/rejが実行されると、対応する処理関数が自動的に実行されるということが分かります.
    三.Promiseのエラーキャプチャー
    エラーが発生した場合、Promiseはエラーを報告しないで、失敗した処理関数then によってエラーを捉えて処理しています.もし私達が自分で書いたPromiseにエラーが発生したら、予期せず直接エラーを報告します.このように.
    実行中にエラーが発生した以上、try/catchを使ってエラーを捕まえられます.
    class Promise {
        constructor (executor) {
            ...
            
            /*   try/catch    ,    reject,      rejected */
            try {
                executor(resolve, reject)
            } catch (error) {
                this.state === 'pending' && reject(error)
            }
        }
        
        then (onFulfilled, onRejected) { ... }
    }
    
    四.then関数詳細
    then関数は二つの特性があります.
  • then関数が実行された後、新しいPromiseのインスタンス
  • に戻る.
  • then関数は、チェーン式で
  • を呼び出すことができます.
    1.thenのチェーン呼び出し
    new Promise(res => res(0))
    .then(value => {
        console.log(value)   // 0
        return `1 fulfilled`
    })
    .then(value => {
        console.log(value)   // 1 fulfilled
    })
    
    thenの関数が実行された後、Promiseの例に戻り、Promiseによって次のthenの関数が決定され、このthenの例に従って、対応する処理関数が実行され、図を描く.
    次の方Promiseの実行は前のものに依存する.thenは、リターンを実行する.then例で、これはPromiseの例のデータは前のデータによる.Promiseの処理関数thenonFulfilled/onRejected決定
    2.thenの処理関数の戻り値はPromiseの例ではない.
    文字どおりにコードを書けば
    class Promise {
        constructor (executor) { ... }
        
        then (onFulfilled, onRejected) {
            /*     Promise   */
            const newPromise = new Promise ( (res, rej) => {})
            
            ...
            
            return newPromise
        }
    }
    
    このように書くと意味がないので、 に戻る状態は常にPromiseであり、状態関数pendingを実行していないので、res/rej関数のチェーンコールもできない.thennew Promise(executor)関数は同期して実行されるので、このように書いてもいいです.
    class Promise {
        constructor (executor) { ... }
        
        then (onFulfilled, onRejected) {
            const newPromise = new Promise ( (res, rej) => {
            
                /* 
                                  ,            
                          res/rej     Promise      
                */
                if (this.state === 'fulfilled') {
                    setTimeout(() => {
                    
                        /*               */
                        const value = onFulfilled(this.value)
                        /*      Promise             */
                        res(value)
                    
                        
                    }, 0);
                }
                if (this.state === 'rejected') {
                    setTimeout(() => {
                        const reason = onRejected(this.reason)
                        res(reason)
                    }, 0);
                }
        
                if (this.state === 'pending') {
                    onFulfilled && this.resolveCallBackList.push( () => {
                        const value = onFulfilled(this.value)
                        res(value)
                    })
                    onRejected && this.rejectCallBackList.push( () => {
                        const reason = onRejected(this.reason)
                        res(reason)
                    })
                }
            })
            
            return newPromise
        }
    }
    
    パカパカexecutorのチェーン呼び出しが完了しました.
    ps:thenの処理関数戻り値がthenの例ではない場合、Promiseまたはfullfilledは、次のrejectedの関数を実行するthenである.
    3.thenの処理関数の戻り値はPromiseの例である.onFulfilledの処理関数の戻り値がthenの例である場合、次のPromiseの関数の実行は、このthenの例によって全部決定されるので、Promiseの関数を使って、戻り値の種類を判断し、対応する場合を処理する必要がある.
    class Promise {
        constructor (executor) { ... }
        
        /* 
            promise -> Promise   
            target -> then           
            res/rej ->     Promise       
        */
        checkReturnValueIfPromise (promise, target, res, rej) {
            if (target instanceof promise) {
            
                /* 
                       Promise  
                       then  ,  Promise              
                            Promise     
                               ,        
                        target.then( value => {
                            res(value)
                        }, reason => {
                            rej(reason)
                        } )
                */
                target.then(res, rej)
            
                
            } else {
                res(target)
            }
        }
        
        then (onFulfilled, onRejected) {
            const newPromise = new Promise ( (res, rej) => {
                if (this.state === 'fulfilled') {
                    setTimeout(() => {
                    
                        const value = onFulfilled(this.value)
                        /*              */
                        this.checkReturnValueIfPromise(Promise, value, res, rej)
                        
                    }, 0);
                }
                if (this.state === 'rejected') {
                    setTimeout(() => {
                        const reason = onRejected(this.reason)
                        this.checkReturnValueIfPromise(Promise, reason, res, rej)
                    }, 0);
                }
        
                if (this.state === 'pending') {
                    onFulfilled && this.resolveCallBackList.push( () => {
                        const value = onFulfilled(this.value)
                        this.checkReturnValueIfPromise(Promise, value, res, rej)
                    })
                    onRejected && this.rejectCallBackList.push( () => {
                        const reason = onRejected(this.reason)
                        this.checkReturnValueIfPromise(Promise, reason, res, rej)
                    })
                }
            })
            
            return newPromise
        }
    }
    
    異歩でも欠点は一つもない.
    五.一部のPromise上の方法(直接コード)
    そうだ、もう一つの方法があります.checkReturnValueIfPromiseと同様の方法thenがあります.この方法はcatchの状態を専門に扱うものです.コードは一言だけです.
    class Promise {
        constructor () { ... }
        
        then () { ... }
        
        catch (onRejected) {
            this.then(undefined, onRejected)
        }
    }
    
    1.Promise.resoverejected状態のfulfilled例を返します.
    class Promise {
        constructor () { ... }
        
        then () { ... }
        
        catch () { ... }
        
        static resolve (value) {
            return new Promise( res => res(value))
        }
    }
    
    2.Promise.rejectPromise状態のrejected例を返します.
    class Promise {
        constructor () { ... }
        
        then () { ... }
        
        catch () { ... }
        
        static resolve () { ... }
        
        static reject (reason) {
            return new Promise( (undefined, rej) => rej(reason))
        }
    }
    
    3.Promise.racePromiseの例を受信した配列Promiseは、promiseArrayの例に戻り、Promiseの中で最も速いPromiseの実施例によって決定される.
    class Promise {
        constructor () { ... }
        
        then () { ... }
        
        catch () { ... }
        
        static resolve () { ... }
        
        static reject () { ... }
        
        static race (promiseArray) {
            return new Promise ( (res, rej) => {
                promiseArray.forEach( promise => {
                    promise.then(res, rej)
                })
            }) 
        }
    }
    
    4.Promise.all
    機能の説明が長すぎて、わからないのは阮一峰先生のPromise.allに対する紹介を見ることができます.
    class Promise {
        constructor () { ... }
        
        then () { ... }
        
        catch () { ... }
        
        static resolve () { ... }
        
        static reject () { ... }
        
        static race () { ... }
        
        static all (promiseArray) {
            let count = 0,
                resultArray = []
            
            return new Promise( (res, rej) => {
                promiseArray.forEach( promise => {
                    promise.then( value => {
                        count++
                        resultArray.push(value)
                        if (count === promiseArray.length) {
                            res(resultArray)
                        }
                    }, reason => {
                        rej(reason)
                    })
                })
            })
        }
    }
    
    六.完全なコード(注釈付き)
    上のコードも完璧ではありません.細かい問題は解決されていませんが、核心機能も完成しています.以下には少し完全なコードを提供します.
    class Promise {
        constructor (executor) {
            if (typeof executor !== 'function') {
                throw new TypeError(`Promise resolver ${executor} must be a function`)
            }
    
            this.state = 'pending'
            this.value = undefined
            this.reason = undefined
            /*    , then()       (onFulfilled, onRejected)      */
            this.resolveCallBackList = []
            this.rejectCallBackList = []
    
            /**
             * @method resolve
             * @param {string} value       
             * @function     ,     ,             
             * @returns {undefined}
             */
            const resolve = value => {
                if (this.state === 'pending') {
                    this.state = 'fulfilled'
                    this.value = value
                    this.resolveCallBackList.length > 0 && this.resolveCallBackList.forEach(e => e())
                }
            }
    
            /**
             * @method reject
             * @param {string} reason       
             * @function     ,     ,             
             * @returns {undefined}
             */
            const reject = reason => {
                if (this.state === 'pending') {
                    this.state = 'rejected'
                    this.reason = reason
                    this.rejectCallBackList.length > 0 && this.rejectCallBackList.forEach(e => e())
                }
            }
    
            /*    Promise      ,          rejected */
            try {
                executor(resolve, reject)
            } catch (error) {
                this.state === 'pending' && reject(error)
            }
        }
    
        /**
         * @method checkLastThenReturn
         * @param {Promise} promise Promise  
         * @param {*} target then    (onFulfilled/onRejected)    
         * @param {function} res    Promise           
         * @param {function} rej    Promise           
         * @function   then()              Promise  
         */
        checkLastThenReturn (promise, target, res, rej) {
            if (target instanceof promise) {
                /*   target      Promise  ,         ,     Promise      */
                target.then(res, rej)
            } else {
                /*     ,                */
                res(target)
            }
        }
    
        /**
         * @method then
         * @param {function} onFulfilled    fulfilled   
         * @param {function} onRejected    rejected   
         * @function      Promise  ,  Promise     ,   then()       
         * @returns {Promise}       Promise  
         */
        then (onFulfilled, onRejected) {
            /*    then()              ,           */
            if (!onFulfilled && !onRejected && this.state === 'pending') return this
    
            /*    then(onFulfilled)             rejected   ,          Promise   */
            if (!onRejected && this.state === 'rejected') return Promise.reject(this.reason)
    
            /*    then()      ,                  */
            if (!onFulfilled && !onRejected) {
                onFulfilled = value => value
                onRejected = reason => reason
            }
    
            /**
             * @method returnPromise
             * @param {function} res     Promise       fulfilled
             * @param {function} rej     Promise       rejected
             * @function     ,  then()     ,  res   rej,   Promise                
             */
            const returnPromise = new Promise( (res, rej) => {
                /* then()          */
                if (this.state === 'fulfilled') {
                    /*   setTimeout   then()          */
                    setTimeout(() => {
                        /*         ,     Promise    rejected */
                        try {
                            const value = onFulfilled(this.value)
                            this.checkLastThenReturn(Promise, value, res, rej)
                        } catch (error) {
                            rej(error)
                        }
                    }, 0);
                }
                if (this.state === 'rejected') {
                    setTimeout(() => {
                        try {
                            const reason = onRejected(this.reason)
                            this.checkLastThenReturn(Promise, reason, res, rej)
                        } catch (error) {
                            rej(error)
                        }
                    }, 0);
                }
    
                /* then()         */
                if (this.state === 'pending') {
                    onFulfilled && this.resolveCallBackList.push( () => {
                        try {
                            const value = onFulfilled(this.value)
                            this.checkLastThenReturn(Promise, value, res, rej)
                        } catch (error) {
                            rej(error)
                        }
                    })
    
                    if (onRejected) {
                        this.rejectCallBackList.push( () => {
                            try {
                                const reason = onRejected(this.reason)
                                this.checkLastThenReturn(Promise, reason, res, rej)
                            } catch (error) {
                                rej(error)
                            }
                        })
                    } else {
                        /*      onRejected,       Promise         */
                        this.rejectCallBackList.push( () => {
                            // Promise.reject(this.reason).catch(rej)
                            rej(this.reason)
                        })
                    }
                }
            })
    
            return returnPromise
        }
    
        /**
         * @method catch
         * @param {function} onRejected         
         * @function   onFulfilled  then()
         * @returns {Promise}
         */
        catch (onRejected) {
            this.then(undefined, onRejected)
        }
    
        /**
         * @method reject
         * @param {string} reason 
         * @function          Promise  
         * @returns {new Promise}
         */
        static reject (reason) {
            return new Promise( (undefined, rej) => rej(reason))
        }
    
        /**
         * @method resolve
         * @param {string} value 
         * @function          Promise  
         * @returns {new Promise}
         */
        static resolve (value) {
            return new Promise( res => res(value))
        }
    
        /**
         * @method race
         * @param {array} promiseArray Promise     
         * @function   Promise           Promise  
         * @returns     Promise  ,        Promise             Promise    
         */
        static race (promiseArray) {
            return new Promise ( (res, rej) => {
                promiseArray.forEach( promise => {
                    promise.then(res, rej)
                })
            }) 
        }
        
        /**
         * @method all
         * @param {array} promiseArray Promise      
         * @function  Promise        Promise                       ,    ,          Promise   
         * @returns      Promise  
         */
        static all (promiseArray) {
            let count = 0,
                resultArray = []
            
            return new Promise( (res, rej) => {
                promiseArray.forEach( promise => {
                    promise.then( value => {
                        count++
                        resultArray.push(value)
                        if (count === promiseArray.length) {
                            res(resultArray)
                        }
                    }, reason => {
                        rej(reason)
                    })
                })
            })
        }
    }
    
    七.結語
    私の文章を読んでくれてありがとうございます.勉強になりますように.