JavaScript Promise由淺入深

認真看完這篇文章, 您能夠本身封裝一個簡易但功能相對齊全的Promise, 還能夠加深對Promise的理解es6

建議 : 看這篇文章以前但願您數組


文章較長, 代碼連貫性較強, 從簡單開始入手, 讀者能夠按需選讀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插件bash

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

二. 異步的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執行時, 自動執行對應的處理函數ui

三. Promise的錯誤捕捉

當發生錯誤時, Promise不會報錯, 而是由失敗的處理函數then函數的第二個函數捕捉錯誤並處理, 若是咱們本身寫的Promise發生錯誤的話, 毫無心外是直接報錯的, 就像這樣this

既然執行時發生錯誤, 那麼咱們就可使用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實例的數據由上一個 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函數的鏈式調用

由於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
    }
}
複製代碼

噠噠, 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
    }
}
複製代碼

就算是異步也是一點毛病都沒有

五. 一些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)
            })
        }) 
    }
}
複製代碼

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

六. 結語

謝謝瀏覽個人文章, 但願你能學到東西

相關文章
相關標籤/搜索