JavaScript Promise由淺入深

xuyede發表於2019-03-05

認真看完這篇文章, 您可以自己封裝一個簡易但功能相對齊全的Promise, 還可以加深對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外掛

JavaScript Promise由淺入深
JavaScript Promise由淺入深

根據Promise的狀態函式resrej,對應執行then中的處理函式onFulfilledonRejected

二. 非同步的Promise

1. then()為非同步

我們都知道,Promise中的then函式的程式碼是非同步執行的,而我們寫的這個並不是,可以驗證一下

JavaScript Promise由淺入深

顯然這段程式碼是同步執行的,而我們想要的輸出順序是 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);
        }
    }
}
複製程式碼

JavaScript Promise由淺入深
ok, 完美得到我們想要的!

2. 狀態函式非同步執行

JavaScript Promise由淺入深
當狀態函式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)
            })
        }
    }
}
複製程式碼

JavaScript Promise由淺入深

這樣, 狀態函式非同步執行的時候也可以處理了, 可以簡單理解為, 當狀態為pending時, 把處理函式onFulfilled/onRejected存起來, 等狀態函式res/rej執行時, 自動執行對應的處理函式

三. Promise的錯誤捕捉

當發生錯誤時, Promise不會報錯, 而是由失敗的處理函式then函式的第二個函式捕捉錯誤並處理, 如果我們自己寫的Promise發生錯誤的話, 毫無意外是直接報錯的, 就像這樣

JavaScript 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) { ... }
}
複製程式碼

JavaScript Promise由淺入深

四. 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例項執行相應的處理函式, 畫個圖

JavaScript Promise由淺入深
下一個then的執行依賴於上一個then執行返回的Promise例項, 而這個Promise例項的資料由上一個then的處理函式onFulfilled/onRejected執行和其返回值決定

2.then的處理函式返回值不是一個Promise例項

如果按照字面意思去寫程式碼

class Promise {
    constructor (executor) { ... }
    
    then (onFulfilled, onRejected) {
        /* 一個新的Promise例項 */
        const newPromise = new Promise ( (res, rej) => {})
        
        ...
        
        return newPromise
    }
}
複製程式碼

如果這樣寫, 是沒意義的, 返回的Promise例項的狀態永遠為pending, 因為沒有執行狀態函式res/rej, 因此也無法進行then函式的鏈式呼叫

JavaScript Promise由淺入深

因為new Promise(executor)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
    }
}
複製程式碼

JavaScript Promise由淺入深
噠噠, then的鏈式呼叫完成了

ps : then的處理函式返回值不是一個Promise例項時, 無論fullfilled還是rejected, 都是執行下一個then函式的onFulfilled

3.then的處理函式返回值是一個Promise例項

then的處理函式返回值是一個Promise例項時, 則下一個then函式的執行, 全部由這個Promise例項決定, 所以我們需要使用checkReturnValueIfPromise函式去判斷一下返回值的型別並處理對應的情況

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
    }
}
複製程式碼

JavaScript Promise由淺入深
就算是非同步也是一點毛病都沒有

JavaScript Promise由淺入深

五. 一些Promise上的方法 (直接上程式碼)

對了, 還有一個與then類似的方法catch, 這個方法是專門處理rejected狀態的, 程式碼也就只有一句話

class Promise {
    constructor () { ... }
    
    then () { ... }
    
    catch (onRejected) {
        this.then(undefined, onRejected)
    }
}
複製程式碼

1. Promise.resolve

返回一個fulfilled狀態的Promise例項

class Promise {
    constructor () { ... }
    
    then () { ... }
    
    catch () { ... }
    
    static resolve (value) {
        return new Promise( res => res(value))
    }
}
複製程式碼

2. Promise.reject

返回一個rejected狀態的Promise例項

class Promise {
    constructor () { ... }
    
    then () { ... }
    
    catch () { ... }
    
    static resolve () { ... }
    
    static reject (reason) {
        return new Promise( (undefined, rej) => rej(reason))
    }
}
複製程式碼

3. Promise.race

接收一個Promise例項的陣列promiseArray, 返回一個Promise例項, 返回的Promise例項由promiseArray中執行最快的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)
            })
        }) 
    }
}
複製程式碼

JavaScript Promise由淺入深

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)
                })
            })
        })
    }
}
複製程式碼

JavaScript Promise由淺入深

JavaScript Promise由淺入深

六. 完整的程式碼 (帶註解)

上面的程式碼也不是完美的, 還有一些細枝末節的問題沒解決, 不過也完成了核心的功能, 下面給出稍微完整的程式碼(帶註解)

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)
                })
            })
        })
    }
}
複製程式碼

七. 結語

謝謝瀏覽我的文章, 希望你能學到東西

相關文章