手寫 Promise 詳細解讀

qwer ?發表於2020-01-03

用法 ?

let p = new Promise((resolve, reject) => {
    if (success) resolve('ok')
    else reject('error')
})

p.then(
    res => console.log('成功', res),
    err => console.log('失敗', err)
    )
    .catch(err => console.log('失敗', err))
    .finally(() => console.log('無論成功失敗都執行'))
    
Promise.resolve('ok')
Promise.reject('error')

Promise.all([p1, p2, p3]).then(res => {
    // res 每個 promise 的結果組成的陣列
})

Promise.race([p1, p2, p3]).then(res => {
    // 最早完成的 promise 的結果
})

複製程式碼

原理 ?

  • Promise 的狀態只能從 pending => resolved 或 從 pending => rejected,狀態一旦改變不會再變
  • 要建立一個 Promise物件,需要new Promise(executor),並傳入一個 executor,該函式預設有兩個方法引數(resolve, reject),在executor內執行使用者邏輯(非同步或同步程式碼),執行完根據成功/失敗情況呼叫resolve/reject
  • Promise會根據Promise物件當前的狀態決定走.then中的 onFullfilled/onRejected(使用者傳入的成功處理函式、失敗處理函式)
  • Promise物件可以無限.then則代表每次返回的都是一個新的Promise物件,每個.then函式返回的data都會作為下一個.then函式的引數

基本實現 ??‍?

const PENDING = 'PENDING'
const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'

class Promise {
    constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        // resolve 方法,將 Promise 物件的狀態改變為成功,並將成功值賦給 value 供給 .then方法的 onFullfilled 使用
        let resolve = (value) => {
            if (this.status === PENDING) {
                this.status = RESOLVED;
                this.value = value;
            }
        }
        // reject 方法,將 Promise 物件的狀態改變未失敗,並將失敗值賦給 reason 供給 .then 方法的 onRejected 使用
        let reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
            }
        }
        // 捕獲同步程式碼產生的錯誤
        try {
            executor(resolve, reject);
        } catch(e) {
            reject(err)
        }
    }
    // Promise 例項的 then 方法
    then(onFullfilled, onRejected) {
        // 如果狀態是 成功態,執行 成功的回撥
        if (this.status === RESOLVED) {
            onFullfilled(this.value)
        }
        // 如果狀態是 失敗態,執行 失敗的回撥
        if (this.status === REJECTED) {
            onRejected(this.reason)
        }
    }
}
複製程式碼

到這裡實現了最基本的狀態變更、資料儲存及成功或失敗的回撥呼叫,使用一下

let p = new Promise((resolve, reject) => {
    resolve('ok')
})
p.then(res => console.log('成功', res)) // 成功ok
複製程式碼

加強 ?

Promise 中的 executor 支援非同步邏輯,即resolve/reject並不是立即呼叫

// 增加非同步時回撥的儲存
class Promise {
    constructor(props) {
        // ...
        this.onFullfilledCallbacks = []; // 儲存成功回撥
        this.onRejectedCallbacks = []; // 儲存失敗回撥
        let resolve = (value) => {
            // ... 修改狀態,儲存 value 值
            // 依次執行儲存的回撥函式
            // 如果 resolve 的 value 是 Promise,則執行 .then 取到最終值再返回
            if (value instanceof Promise) {
                return value.then(resolve, reject)
            }
            this.onFullfilledCallbacks.forEach(fn => fn());
        }
        let reject = (reason) => {
            // ...
            this.onRejectedCallbacks.forEach(fn => fn());
        }
    }
    then(onFullfilled, onRejected) {
        // ...
        // 如果是非同步,未立即修改 Promise 物件的狀態,儲存使用者傳入的成功、失敗回撥函式,
        if (this.status === PENDING) {
            this.onFullfilledCallbacks.push(() => {
                // aop 切面思想,預留處理其它問題
                onFullfilled(this.value)
            })
            this.onRejectedCallbacks.push(() => {
                onRejected(this.reason)
            })
        }
    }
}
複製程式碼

到這裡實現了存在非同步時,等待返回最終狀態再執行回撥,使用下

new Promise((resolve) => {
    setTimeout(() => {
        resolve('ok')
    },2000)
}).then(res => console.log('2秒後', res)) // 等2秒後輸出結果
複製程式碼

加強 ??

Promise支援無限鏈式呼叫,每次.then又返回一個可以.thenPromise物件

function resolvePromise(promise2, x, resolve, reject) {
    // promise2 === x 會造成迴圈呼叫直接拋錯
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise #<Promise'))
    }
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        try {
            let then = x.then
            if (typeof then === 'function') {
                then.call(x, y => {
                    resolvePromise(promise2, y, resolve, reject)
                }, r => {
                    reject(r)
                })
            } else {
                // 普通物件或普通函式
                resolve(x)
            }
        } catch(e) {
            reject(e)
        }
    } else {
        // 普通值直接返回
        resolve(x)
    }
}

class Promise {
    constructor(props) {
        // ...
    }
    then(onFullfilled, onRejected) {
        // 當未傳入 onFullfilled/onRejected 時,給預設函式
        onFullfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onFulfilled === 'function' ? onRejected: err => { throw err }
        // 每次執行 .then 返回一個新的 Promise 物件
        let promise2 = new Promise((resolve, reject) => {
            if (this.status === RESOLVED) {
                // 新增定時器是因為當處理當前結果時還取不到 promise2
                setTimeout(() => {
                    // 捕獲非同步中出現的錯誤
                    try {
                        // 執行使用者的成功回撥,取到返回結果
                        let x = onFullfilled(this.value)
                        // 對返回結果進行統一處理
                        resolvePromise(promise2, x, resolve, reject)
                    } catch(e) {
                        reject(e)
                    }
                }, 0)
            }
            if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch(e) {
                        reject(e)
                    }
                }, 0)
            }
            
            if (this.status === PENDING) {
                this.onFullfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFullfilled(this.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch(e) {
                            reject(e)
                        }
                    }, 0)
                })
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch(e) {
                            reject(e)
                        }
                    }, 0)
                })
            }
        })
        return promise2;
    }
}
複製程式碼

實現了

  • promise鏈式呼叫
  • then返回Promise物件時取到最終資料的處理
  • 異常捕獲處理

至此 Promise的實現全部完成 ?,如果有幫助請點贊分享,感謝!

參考

相關文章