ES6 Promise 及實現原理

hqq2016發表於2018-08-29

ES6 Promise 及實現原理


Promise的誕生:

js屬於單執行緒,非同步的語言,大部分的非同步操作都是依靠回撥函式進行處理,如果遇到傳送請求依賴上個請求的結果進行下次請求,就會出現巢狀的問題,如下:


var fs = require('fs')
// 每個請求的地址依賴上一個請求
fs.readFile('./name.txt', function (error, data) {
    let agePath = data
    if (data) {
        fs.readFile(agePath, function (error, data) {
            let sexPath = data
            if (data) {
                fs.readFile(sexPath, function (error, data) {
                    console.log(sexPath)
                })
            }
        })
    }
})複製程式碼

大量的巢狀導致程式碼的可讀性變差,維護困難,由此誕生了promise

下面我們來使用一下Promise

let p = new Promise((resolve, reject) => {
    setTimeout(function () {
        resolve('我是p1')
    }, 2000)
})

console.log(promise);

p.then(data => {
    console.log(data)
}, err => {
    console.log(err)
})複製程式碼

首先是new Promise傳入一個構造器,返回一個Promise物件,構造器內有一個resolve和reject方法,分別用來觸發promise的成功失敗,then內有兩個引數,第一個是註冊成功的回撥,第二個是註冊失敗的回撥函式


簡單實現

function Promise(executor) {
    //為什麼要用self
    //因為promise捕獲非同步的結果,resolve和reject用來觸發成功失敗的時候this指向已經改變
    let self = this
    self.status = 'pending'
    self.value = undefined
    self.reason = undefined
    // 成功回撥陣列
    self.onFulfilled = []
    // 失敗回撥陣列
    self.onRejected = []
    // 一旦promise狀態變為成功或者失敗就不會在改變
    function resolve(value) {
        if (self.status !== 'pending') return
        self.status = 'resolved'
        self.value = value
        self.onFulfilled.forEach(fn => fn())
    }
    function reject(reason) {
        if (self.status !== 'pending') return
        self.status = 'rejected'
        self.reason = reason
        self.onRejected.forEach(fn => fn())
    }
    // 捕獲錯誤,如果Promise例項化出現錯誤直接執行reject
    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

// then函式用來註冊成功失敗的回撥
Promise.prototype.then = function (onFulfilled, onRejected) {
    let self = this
    // 還在等待結果的時候需要註冊到回撥陣列內
    if (self.status === 'pending') {
        self.onFulfilled.push(() => {
            onFulfilled(self.value)
        })
        self.onRejected.push(() => {
            onRejected(self.reason)
        })
    }
    // 如果呼叫.then的時候狀態已經是resolved了
    // 我們直接呼叫方法,因為之前的陣列可能執行過了
    // 情況出現在回撥完成後,再次呼叫then的方法
    if (self.status === 'resolved') {
        onFulfilled(self.value)
    }
    // 同理上面
    if (self.status === 'rejected') {
        onRejected(self.reason)
    }
}複製程式碼


以上程式碼實現簡單的Promise

Promise還支援鏈式呼叫,我們來看看鏈式呼叫的實現方法,需要修改then方法

function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        // 防止迴圈引用死迴圈
        // 例如
        // let p = new Promise((resolve, reject) => {
        //     resolve(123)
        // })
        // p.then(data => {
        //     return p
        // })
        return reject(new TypeError('迴圈引用'))
    }
    // 建立變數用來標記,如果進入成功就不會觸發失敗,防止引用其他人的Promise庫同時觸發成功和失敗
    let called
    // 如果返回值是函式或者是物件,就有可能是其他人return的Promise物件,我們需要等他成功後將結果返回到下一個Promise中
    // 如果返回值是其他型別,直接丟擲到下個then的引數中
    if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
        // 捕獲異常,如果then函式存在錯誤,直接丟擲
        try {
            let then = x.then
            if (typeof then === 'function') {
                then.call(x, (y) => {
                    // 遞迴處理,防止返回值還是Promise,直到處理完型別不是函式或者物件丟擲
                    resolvePromise(promise2, y, resolve, reject)
                }, (e) => {
                    if (called) return
                    called = true
                    reject(e)
                })
            } else {
                // 不是函式結果
                if (called) return
                called = true
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true
            reject(e)
        }
    } else {
        if (called) return
        called = true
        resolve(x)
    }
}

// then函式用來註冊成功失敗的回撥
Promise.prototype.then = function (onFulfilled, onRejected) {
    let self = this
    // 建立一個promise2作為返回值
    let promise2
    promise2 = new Promise((resolve, reject) => {
        // 還在等待結果的時候需要註冊到回撥陣列內
        if (self.status === 'pending') {
            self.onFulfilled.push(() => {
                let x = onFulfilled(self.value)
                resolvePromise(promise2, x, resolve, reject)
            })
            self.onRejected.push(() => {
                let x = onRejected(self.reason)
                resolvePromise(promise2, x, resolve, reject)
            })
        }
        // 如果呼叫.then的時候狀態已經是resolved了
        // 我們直接呼叫方法,因為之前的陣列可能執行過了
        // 情況出現在回撥完成後,再次呼叫then的方法
        if (self.status === 'resolved') {
            let x = onFulfilled(self.value)
            resolvePromise(promise2, x, resolve, reject)
        }
        // 同理上面
        if (self.status === 'rejected') {
            let x = onRejected(self.reason)
            resolvePromise(promise2, x, resolve, reject)
        }
    })
    return promise2
}


new Promise((resolve, reject) => {
    resolve(123)
}).then(data => {
    console.log(data)  //輸出123
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(456)
        }, 1000)
    })
}).then(data => {
    console.log(data)  //輸出456
})複製程式碼


resolvePromise是一個處理函式,當上一個Promise成功後會呼叫下一個Promise的resolve, reject來觸發下一個Promise的回撥

現在我們來完善一下程式碼


// then函式用來註冊成功失敗的回撥
Promise.prototype.then = function (onFulfilled, onRejected) {
    // 如果沒有傳參,預設返回上一個結果到下個Promise中,在下個Promise的then中呼叫
    if (typeof onFulfilled !== 'function') onFulfilled = data => data
    // 錯誤必須throw錯誤不然返回出來就是上一個Promise的成功了,throw會被try,catch捕獲,執行reject
    if (typeof onRejected !== 'function') onRejected = err => {
        throw err
    }
    let self = this
    // 建立一個promise2作為返回值
    let promise2
    promise2 = new Promise((resolve, reject) => {
        // 還在等待結果的時候需要註冊到回撥陣列內
        if (self.status === 'pending') {
            self.onFulfilled.push(() => {
                try {
                    let x = onFulfilled(self.value)
                    // 處理函式,當上一個Promise成功後會呼叫下一個Promise的resolve, reject來觸發下一個Promise的回撥
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            })
            self.onRejected.push(() => {
                try {
                    let x = onRejected(self.reason)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            })
        }
        // 如果呼叫.then的時候狀態已經是resolved了
        // 我們直接呼叫方法,因為之前的陣列可能執行過了
        // 情況出現在回撥完成後,再次呼叫then的方法
        if (self.status === 'resolved') {
            try {
                let x = onFulfilled(self.value)
                resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
                reject(e)
            }
        }
        // 同理上面
        if (self.status === 'rejected') {
            try {
                let x = onRejected(self.reason)
                resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
                reject(e)
            }
        }
    })
    return promise2
}

// 直接執行一個只有失敗回撥的函式
// 如果前面沒有寫失敗回撥,會throw到後面有失敗回撥或者是catch內
Promise.prototype.catch = function (onRejected) {
    this.then(null, onRejected)
}複製程式碼


之前說的處理都是按順序處理非同步,如果同時執行多個非同步函式,Promise有一個Promise.all方法,專門用來處理非同步,傳入一個Promise陣列,返回一組Promise的結果,如果失敗一個就進入失敗回撥


let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    }, 500)
})

let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(2)
    }, 500)
})
Promise.all([p1, p2]).then(list => {
    console.log(list)
}, err => {
    console.log(err)
})複製程式碼


我們接下來看看實現Promise.all的實現


Promise.all = function (promises) {
    let result = []
    let index = 0
    return new Promise((resolve, reject) => {
        function processData(i, data) {
            result[i] = data
            index++
            if (index === promises.length) {
                resolve(result)
            }
        }
        promises.forEach((promise, i) => {
            promise.then(data => {
                processData(i, data)
            }, reject)
        })
    })
}複製程式碼

按順序執行,resolve後把結果賦值給result對應的索引,然後當index和Promise長度一樣講陣列丟出到返回Promise中,返回順序和輸入保持一致


Promise的實現原理大致就是這樣了,雖然寫法是這樣的,但是本質還是通過callback實現,但是大大提升了程式碼的可讀性。


相關文章