Day6 - 實現一個本身的Promise(完整版)

前言

網上已經有不少前輩寫過該如何一步步的實現一個符合Promise/A+規範的文章了javascript

可是呢java

無論看多少篇、多少遍,真的不如本身對照規範一行一行手動敲一遍來的實在~ 😂😂😂es6

建議是多看幾遍規範,認真理解以後再開始一個功能一個功能的實現~npm

封面來源——ivanjov.com數組

參考

阮一峯老師的文章 Promise對象promise

Promises/A+規範原文異步

【翻譯】Promises/A+規範函數

完整版

/** * 根據Promise/A+規範,實現本身的Promise * * Promise 表示一個異步操做的最終結果,與之進行交互的方式主要是 then 方法, * 該方法註冊了兩個回調函數,用於接收 promise 的終值或本 promise 不能執行的緣由。 * * 一個 Promise 的當前狀態必須爲如下三種狀態中的一種:等待態(Pending)、執行態(Fulfilled)和拒絕態(Rejected) * 而且只能由Pending -> Fulfilled 或者 Pending -> Rejected,且必須擁有一個不可變的終值或拒因 * * 一個 promise 必須提供一個 then 方法以訪問其當前值、終值和據因。 */

const PENDING = 'pending'
const FULFILLED = 'fulFilled'
const REJECTED = 'rejected'

function Promise (excutor) {
    const that = this
    that.status = PENDING
    that.value = undefined
    that.reason = undefined
    // 存儲fulFilled狀態對應的onFulfilled函數
    that.onFulfilledCallbacks = []
    // 存儲rejected狀態對應的onRejected函數
    that.onRejectedCallbacks = []

    /** * @param {*} value 成功態接收的終值 * * 爲何resolve 加setTimeout? * 一 2.2.4規範 要確保 onFulfilled 和 onRejected 方法異步執行 * (且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行) 因此要在resolve里加上setTimeout * * 二 2.2.6規範 對於一個promise,它的then方法能夠調用屢次.(當在其餘程序中屢次調用同一個promise的then時 * 因爲以前狀態已經爲FULFILLED/REJECTED狀態,則會走的下面邏輯),因此要確保爲FULFILLED/REJECTED狀態後 也要異步執行onFulfilled/onRejected * * onFulfilled 和 onRejected 必須被做爲函數調用(即沒有 this 值),且只容許在執行環境堆棧僅包含平臺代碼時運行 * 對應規範中 2.2.4 * * 這裏的平臺代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保 onFulfilled * 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。 */
    function resolve (value) {
        // 解決resolve方法嵌套返回promise的問題
        if (value instanceof Promise) {
            return value.then(resolve, reject)
        }
        setTimeout(() => {
            if (that.status === PENDING) {
                // 只能由 pending -> fulfilled狀態 (避免調用屢次resolve reject)
                that.status = FULFILLED
                that.value = value
                // 分別執行成功狀態訂閱器中的回調方法
                that.onFulfilledCallbacks.forEach(cb => cb(that.value))
            }
        })
    }

    /** * 爲何reject中不用判斷reason類型? * @param {*} reason 失敗態接收到拒因 */
    function reject (reason) {
        setTimeout(() => {
            if (that.status === PENDING) {
                // 只能由 pending -> rejected狀態 (避免調用屢次resolve reject)
                that.status = REJECTED
                that.reason = reason
                // 分別執行訂失敗狀態閱器中的回調方法
                that.onRejectedCallbacks.forEach(cb => cb(that.reason))
            }
        })
    }

    // 捕獲excutor執行器中的異常
    try{
        excutor(resolve, reject)
    } catch (err) {
        reject(err)
    }
}

/** * 註冊fulfilled狀態/rejected狀態的回調函數 * @param {Function} onFulfilled fulfilled狀態執行的函數 * @param {Function} onRejected rejected狀態執行的函數 * @returns {Function} newPromise 返回一個新的promised */
Promise.prototype.then = function (onFulfilled, onRejected) {
    /** * 處理參數默認值,保證後續能夠繼續執行 * 對應規範中 2.2.1 若是 onFulfilled / onRejected 不是函數,其必須被忽略 * */
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
        throw reason;
    };
    
    /** * then裏面的FULFILLED/REJECTED狀態時, 爲何要加setTimeout? * */
    let that = this
    let promise2
    if (that.status === FULFILLED) {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try{
                    let x = onFulfilled(that.value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })
        })
    }

    if (that.status === REJECTED) {
        return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try{
                    let x = onRejected(that.reason)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })
        })
    } 
    
    if (that.status === PENDING) {
        // 這裏是爲了解決異步的問題,採用發佈訂閱的方式,下面兩個數組分別存儲成功和失敗的回調
        // 返回一個Promise是爲了解決能夠鏈式調用的問題

        return promise2 = new Promise((resolve, reject) => {
            that.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value)
                    // 解析promise流程
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })

            that.onRejectedCallbacks.push((reason) => {
                try {
                    let x = onRejected(reason)
                    // 解析promise流程
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })

        })
    }
}

function resolvePromise (promise2, x, resolve, reject) {
    // console.log(this)
    // 若是 promise 和 x 指向同一對象,會致使循環引用報錯,So 以 TypeError 爲據因拒絕執行 promise
    // 對應規範中 2.3.1
    if (promise2 === x) {
        return reject(new TypeError('循環引用'))
    }
    // promise2是否已經resolve或者reject,避免重複調用
    let called = false
    // 若是x是一個promise對象,繼續resolve
    // 對應規範中 2.3.2
    if (x instanceof Promise) {
        // 若是是等待狀態,則須要保持等待態直至 x 被執行 / 被拒絕,並解析y值
        // 對應規範中 2.3.2.1
        if (x.status === PENDING) {
            x.then(y => {
                resolvePromise(promise2, y, resolve, reject)
            }, reason => {
                reject(reason)
            })
        } else {
            // 若是x已經處於執行態 / 拒絕態,則用相同的值 / 拒因 執行promise
            // 對應規範中 2.3.2.2 和 2.3.2.3
            x.then(resolve, reject)
        }
        // 若是 x 爲對象或者函數
        // 對應規範中 2.3.3
    } else if (x !== null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try {
            /** * 這步咱們先是存儲了一個指向 x.then 的引用,而後測試並調用該引用,以免屢次訪問 x.then 屬性。 * 這種預防措施確保了該屬性的一致性,由於其值可能在檢索調用時被改變。 * 對應規範中 2.3.3.1 */
            let then = x.then

            /** * 若是 then 是函數,將 x 做爲函數的做用域 this 調用之。 * 傳遞兩個回調函數做爲參數,第一個參數叫作 resolvePromise ,第二個參數叫作 rejectPromise * 對應規範中 2.3.3.3 */
            if (typeof then === 'function') {
                then.call(x, y => {
                    /** * 若是 resolvePromise 和 rejectPromise 均被調用, * 或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用 * 對應規範中 2.3.3.3.3 */
                    if (called) return
                    called = true

                    // 對應規範中 2.3.3.3.1
                    resolvePromise(promise2, y, resolve, reject)
                }, reason => {
                    if (called) return
                    called = true

                    // 若是 rejectPromise 以據因 r 爲參數被調用,則以據因 r 拒絕 promise
                    // 對應規範中 2.3.3.3.2
                    reject(reason)
                })
            } else {
                // 若是 then 不是函數,以 x 爲參數執行 promise
                // 對應規範中 2.3.3.4
                resolve(x)
            }
        } catch (e) {
            // 若是調用 then 方法拋出了異常 e
            // 對應規範中 2.3.3.3.4

            // 若是 resolvePromise 或 rejectPromise 已經被調用,則忽略之
            // 對應規範中 2.3.3.3.4.1
            if (called) return
            called = true

            // 不然以 e 爲據因拒絕 promise
            // 對應規範中 2.3.3.3.4.2
            reject(e)
        }
    } else {
        // 若是 then 不是函數,是一個普通的值,以 x 爲參數執行 promise
        // 對應規範中 2.3.4
        resolve(x)
    }
}

複製代碼

測試

// 在promise實現的代碼中,增長如下執行測試用例須要用到的代碼
Promise.deferred = function() {
    let defer = {};
    defer.promise = new Promise((resolve, reject) => {
        defer.resolve = resolve;
        defer.reject = reject;
    });
    return defer;
}

try {
    module.exports = Promise
} catch (e) {
    console.log(e, '---')
}
複製代碼

// 安裝測試腳本
npm i -g promises-aplus-tests

// 測試命令
promises-aplus-tests Promise.js

// 872 passing
複製代碼

Promise其餘方法

// 馬上返回一個promise,通常用於沒有promise對象,須要將一個東西,轉爲promise
Promise.resolve = function (data) {
    return new Promise(resolve => {
        resolve(data)
    })
}

Promise.reject = function (reason) {
    return new Promise((resolve, reject) => {
        reject(reason)
    })
}


// 接收一個promise數組,所有成功以後才往下執行,並返回一個promise
Promise.all = function (promiseArray) {
    return new Promise((resolve, reject) => {
        let resolveArr = []
        promiseArray.forEach(item => {
            item.then(data => {
                resolveArr.push(data)
                console.log(data, '---data')
                if (promiseArray.length === resolveArr.length) {
                    resolve(resolveArr)
                }
            }, reason => {
                reject(reason)
            })
        })
    })
}


// 接收一個promise數組,只要有一個先返回,不管是resolve仍是reject,都會往下執行then中的成功或者失敗回調,
// 其餘的promise也會繼續執行,可是不會使用結果
Promise.race = function (promiseArray) {
    return new Promise((resolve, reject) => {
        promiseArray.forEach(item => {
            item.then(data => {
                resolve(data)
            }, reason => {
                reject(reason)
            })
        })
    })
}


// 用於捕獲錯誤的回調,即第一個resolve參數爲null的特殊then方法
Promise.prototype.catch = function (reject) {
    return this.then(null, reject)
}


// 不管前面執行結果狀態,都會進入該方法中,且會將值原封不動的傳給後面的then
Promise.prototype.finally = function (callback) {
    return this.then(value => {
        return new Promise(callback()).then(() => {
            return value
        })
    }, reason => {
        return new Promise(callback()).then(() => {
            throw reason
        })
    })
}

複製代碼
相關文章
相關標籤/搜索