面試官你來,130行帶你手寫完整Promise

雷鋒蜀黍發表於2020-04-04

大家好,我是雷鋒蜀黍。一直在某些面試寶典看到面試官要你手擼一個promise,今天天氣這麼好,不如我們來搞一搞。(PS:從未看過別人的實現,本文更像是記錄一個思考的過程)

最終的程式碼完全符合Promise A+規範,並通過所有 promises-aplus-tests 測試用例。先上個測試通過圖得瑟一下,嘿嘿嘿。

如果自己動手實現過Promise的可以直接跳到 從零開始的Promise 章節,看看我的實現,下面我們開始吧。

分析

先回想一下promise是個啥:promise是為了解決回撥地獄而出現的產物,通過例項的then、catch方法進行鏈式呼叫。它本身擁有三種狀態:等待、成功、失敗,並且成功失敗後狀態無法改變。一般使用方式:

new Promise(function (resolve, reject{
    setTimeout(function ({
     resolve(1)
    }, 1000)
})
.then(function (res{
    console.log(res) // 1
    return res
})
.catch(function (res// 不會被呼叫
    console.log(res)
    return res
})
複製程式碼

瀏覽器列印:

promise例項的列印結果
promise例項的列印結果

從中我們可以看到例項上具有[[PromiseStatus]][[PromiseValue]]兩個屬性,分別代表狀態與值。

隱式原型上具有catch、finally、then方法。

輕微試探

先照上面分析的打個樣:

let MyPromise = (function ({
    function resolve(res{
        this['[[PromiseStatus]]'] = 'fulfilled'
        this['[[PromiseValue]]'] = res
    }

    function reject(res{
        this['[[PromiseStatus]]'] = 'rejected'
        this['[[PromiseValue]]'] = res
    }
    return function (fn{
        this['[[PromiseStatus]]'] = 'pending'
        this['[[PromiseValue]]'] = undefined
        fn(resolve.bind(this), reject.bind(this)) 
    }
})()

MyPromise.prototype.then = function (fn{
    // 只有在成功狀態才會呼叫
    if (this['[[PromiseStatus]]'] === 'fulfilled') {
        this['[[PromiseValue]]'] = fn(this['[[PromiseValue]]'])
    }
    // 返回自身 進行鏈式呼叫
    return this
}
MyPromise.prototype.catch = function (fn{
    // 只有在失敗狀態才會呼叫
    if (this['[[PromiseStatus]]'] === 'rejected') {
        this['[[PromiseValue]]'] = fn(this['[[PromiseValue]]'])
    }
    // 返回自身 進行鏈式呼叫
    return this
}
複製程式碼

試著列印輸出:

let a = new MyPromise(function (resolve, reject{
    setTimeout(function ({
        resolve(1)
    }, 1000)
}).then(function (res{
    console.log(res)
    return 2
}).catch(function (res{
    console.log(res)
    return 3
})
console.log(a)
複製程式碼
不具備非同步能力的輸出結果
不具備非同步能力的輸出結果

事情不簡單

emmm,把非同步的問題忘記了,在setTimeout期間狀態一直是pending,所以直接呼叫then是無效的。思路應該是把這些then中的方法進行儲存,在resolve結束時進行呼叫。

我們建立個屬性進行鏈式函式的儲存,需要注意的是存在成功和失敗兩種狀態。

let MyPromise = (function ({
    function resolve(res{
        this['[[PromiseStatus]]'] = 'fulfilled'
        this['[[PromiseValue]]'] = res
       // 模擬非同步 並保證接收到所有then方法後執行
        setTimeout(() => {      
         // 遍歷finishArr並鏈式執行
            while (this.asyncArr.finishArr.length > 0) {
                this.asyncArr.finishArr.shift()()
            }
        }, 0);
        // 幹掉永遠不可能執行的方法
        this.asyncArr.errorArr = []
    }

    function reject(res{
        this['[[PromiseStatus]]'] = 'rejected'
        this['[[PromiseValue]]'] = res
       setTimeout(() => {
       // 遍歷errorArr並鏈式執行
            while (this.asyncArr.errorArr.length > 0) {
                this.asyncArr.errorArr.shift()()
            }
        }, 0);
        // 幹掉永遠不可能執行的方法
        this.asyncArr.finishArr = []
    }
    return function (fn{
        this['[[PromiseStatus]]'] = 'pending'
        this['[[PromiseValue]]'] = undefined
        this.asyncArr = {       // 儲存鏈式呼叫的方法
            finishArr: [],
            errorArr: []
        }
        fn(resolve.bind(this), reject.bind(this))
    }
})()

MyPromise.prototype.then = function (fn{
    // 失敗時直接返回 等待和成功都要壓入陣列中
    ifthis['[[PromiseStatus]]'] === 'rejected')  return this
    this.asyncArr.finishArr.push( ()=> {
        this['[[PromiseValue]]'] = fn(this['[[PromiseValue]]'])
    })
    // 返回自身 進行鏈式呼叫
    return this
}
MyPromise.prototype.catch = function (fn{
    ifthis['[[PromiseStatus]]'] === 'fulfilled')  return this
    this.asyncArr.errorArr.push( ()=> {
        this['[[PromiseValue]]'] = fn(this['[[PromiseValue]]'])
    })
    // 返回自身 進行鏈式呼叫
    return this
}
複製程式碼

歐凱~這樣就解決了非同步的問題,也完全滿足我們常用的幾個功能。但是大家覺得這就結束了嗎?不,這只是開始,還是個錯誤的開始。

大錯特錯

下面我們一起來總結下上面的程式碼在邏輯上存在哪些錯誤

1.promise的狀態無法被“改變”

可能有人就疑惑了,promise的狀態本來就是不能被改變的,這沒有錯啊。那請你來看下面這段程式碼:

let a = new Promise(function (resolve, reject) {
        reject(1)
    })
    .catch(function (res) {
        console.log(res)
        return 3
    })
    .then(function (res) {
        console.log(res)
    })
    console.log(a)  // type: resolved    val: undefined
複製程式碼

你會發現a的狀態從reject變為了resolved,你是否會動搖對promise的認知呢,就覺得別人說的什麼promise不能改變狀態都是扯淡,能變得。

切勿聽風是雨,自己動手試一下才會真的明白。 then方法返回的並不是當前物件自身,而是一個全新的Promise。 promise執行示例圖

2.雞蛋全放一個籃子裡

現在我們將所有的then、catch方法都存放到了第一個promise的屬性上,那如果按照上一條的改進意見,每次都返回一個全新的promise,那這種方式是否可行呢?答案我想是可以的,但是這樣並不好,會造成頭重腳輕,首個promise物件與其他promise物件大不一樣的感覺,所以我想正確的資料結構應該是這樣的。

promise物件結構 通過next進行鏈式呼叫,每個promise僅儲存下一個所指向的promise,並且當前的Promise狀態決定下一個Promise呼叫resolve還是reject,再通過這些方法來決定當前promise的狀態。從此無限套娃~ 同時這樣你也不需要兩個陣列來對成功的方法與錯誤的方法進行分離儲存,全部都是用next指向,這和Promise的概念也是相同的,Promise的狀態僅會被一個改變狀態函式所影響,其餘的都將會忽略。

從零開始的Promise

下面才是本篇文章正真的開始,根據以上總結出的經驗,我們來實現一個完整的並完全符合Promise A+規範的Promise。

最靚的仔
最靚的仔

我們先來總覽一下MDN上描述的Promise方法:

靜態方法:

Promise.all(iterable)

Promise.race(iterable)

Promise.reject(reason)

Promise.resolve(value)

例項方法:

Promise.prototype.catch(onReject)

Promise.prototype.then(onFulfilled,onRejected)

Promise.prototype.finally

具體細節就不贅述了,不太清楚的小夥伴請自行去mdn上進行學習後再回來看具體實現,下面我們開始吧。

Promise建構函式

人狠話不多,先上程式碼再講解

let MyPromise = (function () {
    function resolve(res) {
        // 如果存在狀態則直接返回
        if (this['[[PromiseStatus]]'] != 'pending') return  
        // 如果接收到的值是自己本身則報錯
        if (res === this) {
            throw new Erroe(
            'TypeError: Chaining cycle detected for promise #<Promise>')
        }
        // 如果res 是一個promise物件 則等待其值然後返回
        this['[[PromiseStatus]]'] = 'fulfilled'
        this['[[PromiseValue]]'] = res

        // promise 是一個巨集任務  使用 setTimeout 進行模擬
        setTimeout(() => {
            if (this.next) {
                this.next()
                delete this.next     // 解除關聯關係 防止無法被垃圾回收器回收
            }
        }, 0)
    }
    function reject(res) {
        // 如果存在狀態則直接返回
        if (this['[[PromiseStatus]]'] != 'pending') return  
        this['[[PromiseStatus]]'] = 'rejected'
        this['[[PromiseValue]]'] = res
        setTimeout(() => {
            // 如果有下一個promise則傳值,如果沒有則直接報錯
            if (this.next) {
                this.next()         //  將會建立下一個promise 在then方法中進行定義
                delete this.next     // 解除關聯關係 防止無法被垃圾回收器回收
            } else {
                throw new Error(res)
            }
        }, 0)
    }
    return function (fn) {
        this['[[PromiseStatus]]'] = 'pending'   // 儲存當前promise狀態
        this['[[PromiseValue]]'] = undefined    // 儲存當前promise的值
        this.next = null                        // 建立下一個promise
        // 儲存所有的finally方法 當改promise當用完前執行內部所有方法
        this.finallyArr = []   
        // 呼叫使用者傳入的函式,並將resolve與reject回傳
        fn(resolve.bind(this), reject.bind(this))   
    }
})()
複製程式碼

特別說明

這裡跟大家強調說明一下,使用setTimeout不僅僅是單純為了模仿非同步效果,而是這樣將呼叫next方法變為一個巨集任務,可以確保在鏈式呼叫時,then方法之後進行執行。

這裡將當傳入的引數為promise的處理和finally方法的處理暫時去除了,等下面涉及到時再和大家單獨說明。

Promise.prototype.then

該方法是整個Promise的核心,並且Promise A+規範規定的也是該方法,下面我們繼續先上程式碼,我會把註釋寫全,再和大家討論實現思路。

MyPromise.prototype.then = function (onFulfilled, onRejected) {
    let _self = this    // 記錄前一個promise
    let promise = new MyPromise(function (resolve, reject) {
        // 將方法儲存下來 在上一個的next中進行觸發
        _self.next = () => {
            // 上一個promise的狀態 決定呼叫哪個函式方法
            // 是成功的 則呼叫 onFulfilled
            if (_self['[[PromiseStatus]]'] == 'fulfilled') { 
                // 如果onFulfilled不存在 則將上一個的值直接傳遞給當前全新的promise
                if (typeof onFulfilled !== 'function') {
                    resolve.call(this, _self['[[PromiseValue]]'])
                }
                // 當前promise的狀態由 執行的方法決定
                try {
                    // 將上一個的值進行回傳 
                    let data = onFulfilled(_self['[[PromiseValue]]']) 
                    resolve.call(this, data)   // 成功 則呼叫 resolve方法
                } catch (error) {
                    reject.call(this, error)   // 失敗則呼叫 reject
                }
            }
            // 是失敗的 則呼叫 onRejected
            else if (_self['[[PromiseStatus]]'] == 'rejected') { 
                 // 如果沒有錯誤處理方法則直接報錯 當前的值
                if (typeof onRejected !== 'function') {
                    reject.call(this, _self['[[PromiseValue]]'])
                } 
                // 當前promise的狀態由 執行的方法決定
                try {
                    let data = onRejected(_self['[[PromiseValue]]'])
                    resolve.call(this, data)   // 成功 則呼叫 resolve方法
                } catch (error) {
                    reject.call(this, error)   // 失敗則呼叫 reject
                }
            }
            // 如果是等待狀態 就空
        }
    })
    // 返回一個全新的promise
    return promise
}
}
複製程式碼

上面的程式碼充斥著高階函式和call、apply,可能不太好理解,下面和大家一起探討一下 首先思路和大錯特錯那一章節中是一樣的,即:

當前Promise的執行resolve、reject是由上一個Promise決定的

當前Promise的狀態,是由該Promise的resolve、reject決定的

特別說明

我們在新建的Promise中並未去觸發改變該狀態的函式,而是將其儲存在了上一個Promise的next屬性中,當上一個Promise觸發resolve方法時再去觸發,這樣就完成了非同步的狀態改變。

下面拿一個示例說說整個Promise發生的流程:

new MyPromise(function (resolve) {
    setTimeout(() => {
        resolve(42)
    }, 1000);
})
.then(res =>{
    console.log(res)
})
複製程式碼

第一步:建立一個新的Promise,將一個匿名方法傳入,將會在建構函式中被呼叫,等待一秒後會去觸發resolve方法

...
fn(resolve.bind(this), reject.bind(this))
複製程式碼

第二步:呼叫首個Promise的then方法,建立一個全新的等待狀態的Promise2,請將改變其狀態的方法存在Promise1next屬性中

...
let promise = new MyPromise(function (resolve, reject) {
    _self.next = () => {
        ...
    }
})
return promise
複製程式碼

第三步:第一步的匿名函式setTimeout觸發resolve方法,改變了首個Promise的狀態和值,同時會去檢查是否存在下一個Promise,如果存在則執行

...
if (this.next) {
    this.next()
    delete this.next     // 解除關聯關係 防止無法被垃圾回收器回收
}
...
複製程式碼

第四步:next方法帶著首個Promise的狀態來了,改變Promise2的狀態

...
// 上一個promise的狀態 決定呼叫哪個函式方法
// 是成功的 則呼叫 onFulfilled
if (_self['[[PromiseStatus]]'] == 'fulfilled') {      
    // 如果onFulfilled不存在 則將現有的值傳遞給下一個全新的promise
    if (typeof onFulfilled !== 'function'){
         resolve.call(this, _self['[[PromiseValue]]'])
    }
    // 當前promise的狀態由 執行的方法決定
    // 將上一個的值進行回傳 
    try {
        let data = onFulfilled(_self['[[PromiseValue]]'])   
        resolve.call(this, data)   // 成功 則呼叫 resolve方法
    } catch (error) {
        reject.call(this, error)   // 失敗則呼叫 reject
    }
}
...
複製程式碼

第五步: Promise2resolvereject再次觸發next方法,以此往復


我們再來看看這兩句程式碼:

...
if (typeof onFulfilled !== 'function') {
    resolve.call(this, _self['[[PromiseValue]]'])
}
...
if (typeof onRejected !== 'function') {
    reject.call(this, _self['[[PromiseValue]]'])
}
...
複製程式碼

但凡遇到空的then方法,將會直接將上一個Promise的狀態和值複製,能夠繼續向下傳遞,就像這樣子:

new MyPromise(function (resolve) {
    resolve(42)
})
.then()
.then()
.then(res =>{
    console.log(res)    // 42
})
複製程式碼

對於空的Promise.then()根本無需理會,同樣返回一個全新的空Promise即可

我們再回過頭將reslove方法進行擴充,如果傳入的值是Promise物件呢?我們將等待其完成然後再進行賦值操作。

...
// 如果res 是一個promise物件 則等待其值然後返回
if (res instanceof MyPromise) {
    res.then(val => {
        this['[[PromiseStatus]]'] = 'fulfilled'
        this['[[PromiseValue]]'] = val
    }, err => {
        this['[[PromiseStatus]]'] = 'rejected'
        this['[[PromiseValue]]'] = err
    })
} 
else {
    this['[[PromiseStatus]]'] = 'fulfilled'
    this['[[PromiseValue]]'] = res
}
...
複製程式碼

then方法這是整個Promise實現最難的地方,大家要是沒理解的話可以自己動手跑一下,我會將完整實現全部貼在最後。

Promise.prototype.catch

實現了then方法後,你會發現其實catch方法只是then方法的閹割版,即沒有了成功的回撥方法,只有失敗的方法。實現程式碼很簡單:

MyPromise.prototype.catch = function (onRejected) {
    // 直接呼叫then方法 並返回then返回的 promise
    return MyPromise.prototype.then.call(this, null, onRejected)
}
複製程式碼

沒錯,直接調then方法即可

Promise.prototype.finally

該方法定義了無論Promise失敗與否,都將會執行的函式。之前實現的thencatch方法都是和Promise的狀態與值掛鉤的, 而finally方法不是,只是單純的在一個Promise呼叫結束後觸發的方法,甚至它連回撥引數也沒有。所以我們重新設定一個物件引數來儲存finally的引數。

return function (fn) {
    // 儲存當前promise狀態
    this['[[PromiseStatus]]'] = 'pending'   
    // 儲存當前promise的值
    this['[[PromiseValue]]'] = undefined    
    // 指向下一個pomise
    this.next = null          
    // 儲存所有的finally方法 當改promise當用完前執行內部所有方法
=>  this.finallyArr = []  
    // 呼叫使用者傳入的函式,並將resolve與reject回傳
    fn(resolve.bind(this), reject.bind(this))  
}
複製程式碼

finally方法中進行新增

MyPromise.prototype.finally = function (onFinally) {
    // 鏈式呼叫 但是不會返回新的promise物件
    this.finallyArr.push(onFinally)
    return this
}
複製程式碼

同時注意,finally返回的是自身,與then、catch是不同的。 現在方法都儲存起來了,只要在該Promise執行後呼叫就可以了,resolve函式新增程式碼如下:

...
setTimeout(() => {
    // 遍歷finishArr並鏈式執行
    // 遍歷執行finally 方法
    while (this.finallyArr.length > 0) {
        this.finallyArr.shift()()
    }
    delete this.finallyArr
    if (this.next) {
        this.next()
        delete this.next     // 解除關聯關係 防止無法被垃圾回收器回收
    }
}, 0)
...
複製程式碼

並且當全部執行後就把該屬性刪除,這樣在瀏覽器中列印出來就和原生的一模一樣了,哈哈哈哈哈哈哈。

機靈鬼 到此,Promise的例項方法全部完成,我們來看看靜態方法吧。

Promise.resolve

它返回一個成功狀態的Promise,雖然我感覺它和我在閉包內實現的resolve方法差不多,但是我沒有想到一個很好的辦法來進行整合,如果有小夥伴想到的話請第一時間告知我!~ 程式碼實現很簡單。

MyPromise.resolve = function (value) {
    // 如果是promise型別直接返回
    if (value instanceof MyPromise) return value
    return new MyPromise(function (resolve) {
        resolve(value)
    })
}
複製程式碼

Promise.reject

它與MyPromise.resolve是一樣的道理。

MyPromise.reject = function (value) {
    // 如果是promise型別直接返回
    if (value instanceof MyPromise) return value
    return new MyPromise(function (resolve, reject) {
        reject(value)
    })
}
複製程式碼

MyPromise.race

該方法的定義是但凡接收到的類陣列引數中有一個引數成功了就直接返回包含該引數的Promise,失敗同理,直接上程式碼:

MyPromise.race = function (arr) {
    let promise = new MyPromise(function (resolve, reject) {
        for (let i = 0; i < arr.length; i++) {
            //  如果是promise 則使用then 進行監聽 誰先完成或者失敗就直接改變狀態
            if (arr[i] instanceof MyPromise) {
                arr[i].then(function (res) {
                    resolve(res)
                }, function (err) {
                    reject(err)
                })
            } else {
                 // 如果不是promise 則直接返回結果
                resolve(arr[i])
            }
        }
    })
    return promise
}
複製程式碼

在現在的Promise實現下,去非同步監聽值得變化變的異常簡單,從而能夠判斷誰是第一個完成或失敗的,從而返回它的狀態。

Promise.all

該方法與race相反,所以類陣列中的引數成功了才會返回一個包含它們結果的Promise,失敗則直接返回錯誤訊息。實現起來相較race稍難一點,需要判斷當前類陣列中的引數是否全部完成,但是在then方法下都信手捏來。

MyPromise.all = function (arr) {
    let promise = new MyPromise(function (resolve, reject) {
        let result = [] // 將結果儲存
        let start = 0,  // 開始非同步的數量 
            finish = 0      // 完成非同步的數量
        for (let i = 0; i < arr.length; i++) {
            // 如果是promise 則使用then 進行監聽
            if (arr[i] instanceof MyPromise) {
                ++start // 記錄當前開始了多少個promise
                // 使用then就會被掛起監聽
                arr[i].then(res => {
                    ++finish    // 記錄當前完成了多少個promise
                    result[i] = res
                    // 全部完成則返回成功狀態與資料
                    if (start == finish) resolve(result) 
                }, err => {
                    reject(err)     // 錯了就直接返回
                })
            } else {
                result[i] = arr[i]
            }
        }
        // 如果沒有非同步方法 則直接返回結果
        if (!start) resolve(result)
    })
    return promise
}
複製程式碼

通過在then方法中對startfinish的對比,從而能夠知道當前完成的Promise是否是最後一個,進而返回全部結果。

完整實現程式碼

到這裡就將整個Promise實現啦,同時我個人也使用promises-aplus-tests進行了測試,符合全部規範,大家可以安心食用。


let MyPromise = (function () {
    function resolve(res) {
        if (this['[[PromiseStatus]]'] != 'pending') return  // 如果存在狀態則直接返回
        // 如果接收到的值是自己本身則報錯
        if (res === this) {
            throw new Erroe('TypeError: Chaining cycle detected for promise #<Promise>')
        }
        // 如果res 是一個promise物件 則等待其值然後返回
        if (res instanceof MyPromise) {
            res.then(val => {
                this['[[PromiseStatus]]'] = 'fulfilled'
                this['[[PromiseValue]]'] = val
            }, err => {
                this['[[PromiseStatus]]'] = 'rejected'
                this['[[PromiseValue]]'] = err
            })
        } else {
            this['[[PromiseStatus]]'] = 'fulfilled'
            this['[[PromiseValue]]'] = res
        }

        // promise 是一個巨集任務  使用 setTimeout 進行模擬
        setTimeout(() => {
            // 遍歷finishArr並鏈式執行
            // 遍歷執行finally 方法
            while (this.finallyArr.length > 0) {
                this.finallyArr.shift()()
            }
            delete this.finallyArr
            if (this.next) {
                this.next()
                delete this.next     // 解除關聯關係 防止無法被垃圾回收器回收
            }
        }, 0)
    }

    function reject(res) {
        if (this['[[PromiseStatus]]'] != 'pending') return  // 如果存在狀態則直接返回
        this['[[PromiseStatus]]'] = 'rejected'
        this['[[PromiseValue]]'] = res
        setTimeout(() => {
            // 遍歷執行finally 方法
            while (this.finallyArr.length > 0) {
                this.finallyArr.shift()()
            }
            delete this.finallyArr
            // 如果有下一個promise則傳值,如果沒有則直接報錯
            if (this.next) {
                this.next()
                delete this.next     // 解除關聯關係 防止無法被垃圾回收器回收
            } else {
                throw new Error(res)
            }
        }, 0)
    }
    return function (fn) {
        this['[[PromiseStatus]]'] = 'pending'
        this['[[PromiseValue]]'] = undefined
        this.next = null
        this.finallyArr = []   // 儲存所有的finally方法 當改promise當用完前執行內部所有方法
        fn(resolve.bind(this), reject.bind(this))
    }
})()

MyPromise.resolve = function (value) {
    // 如果是promise型別直接返回
    if (value instanceof MyPromise) return value
    return new MyPromise(function (resolve) {
        resolve(value)
    })
}

MyPromise.reject = function (value) {
    // 如果是promise型別直接返回
    if (value instanceof MyPromise) return value
    return new MyPromise(function (resolve, reject) {
        reject(value)
    })
}

MyPromise.all = function (arr) {
    let promise = new MyPromise(function (resolve, reject) {
        let result = [] // 將結果儲存
        let start = 0,  // 開始非同步數量 
            finish = 0      // 完成非同步的數量
        for (let i = 0; i < arr.length; i++) {
            // 如果是promise 則使用then 進行監聽
            if (arr[i] instanceof MyPromise) {
                ++start // 記錄當前開始了多少個promise
                // 使用then就會被掛起監聽
                arr[i].then(res => {
                    ++finish    // 記錄當前完成了多少個promise
                    result[i] = res
                    if (start == finish) resolve(result) // 全部完成則返回成功狀態與資料
                }, err => {
                    reject(err)     // 錯了就直接返回
                })
            } else {
                result[i] = arr[i]
            }
        }
        // 如果沒有非同步方法 則直接返回結果
        if (!start) resolve(result)
    })
    return promise
}

MyPromise.race = function (arr) {
    let promise = new MyPromise(function (resolve, reject) {
        for (let i = 0; i < arr.length; i++) {
            //  如果是promise 則使用then 進行監聽 誰先完成或者失敗就直接改變狀態
            if (arr[i] instanceof MyPromise) {
                arr[i].then(function (res) {
                    resolve(res)
                }, function (err) {
                    reject(err)
                })
            } else {
                 // 如果不是promise 則直接返回結果
                resolve(arr[i])
            }
        }
    })
    return promise
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
    let _self = this    // 記錄前一個promise
    let promise = new MyPromise(function (resolve, reject) {
        // 將方法儲存下來 在上一個的next中進行改變狀態操作
        _self.next = () => {
            // 上一個promise的狀態 決定呼叫哪個函式方法
            // 當前是成功的 則呼叫 onFulfilled
            if (_self['[[PromiseStatus]]'] == 'fulfilled') {      
                // 如果onFulfilled不存在 則將現有的值傳遞給下一個全新的promise
                if (typeof onFulfilled !== 'function') {
                    resolve.call(this, _self['[[PromiseValue]]'])
                }
                // 當前promise的狀態由 執行的方法決定
                try {
                    let data = onFulfilled(_self['[[PromiseValue]]'])
                    resolve.call(this, data)   // 成功 則呼叫 resolve方法
                } catch (error) {
                    reject.call(this, error)   // 失敗則呼叫 reject
                }
            }
            // 當前是失敗的 則呼叫 onRejected
            else if (_self['[[PromiseStatus]]'] == 'rejected') {  
                // 如果沒有錯誤處理方法則直接報錯 當前的值
                if (typeof onRejected !== 'function') {
                    reject.call(this, _self['[[PromiseValue]]'])  
                }
                // 當前promise的狀態由 執行的方法決定
                try {
                    let data = onRejected(_self['[[PromiseValue]]'])
                    resolve.call(this, data)   // 成功 則呼叫 resolve方法
                } catch (error) {
                    reject.call(this, error)   // 失敗則呼叫 reject
                }
            }
            // 如果是等待狀態 就空
        }
    })
    // 需要返回一個全新的promise 並且這個promise就是當前處理的這個
    return promise
}

MyPromise.prototype.catch = function (onRejected) {
    // 直接呼叫then方法 並返回then返回的 promise
    return MyPromise.prototype.then.call(this, null, onRejected)
}

MyPromise.prototype.finally = function (onFinally) {
    // 鏈式呼叫 但是不會返回新的promise物件
    this.finallyArr.push(onFinally)
    return this
}

Promise.deferred = Promise.defer = function () {
    var dfd = {}
    dfd.promise = new Promise(function (resolve, reject) {
        dfd.resolve = resolve
        dfd.reject = reject
    })
    return dfd
}

module.exports = Promise
複製程式碼

Promise.deferred是給測試提供的介面,無視。

結語

我還記得那是一個晴朗的早晨,外面天氣很好,我看到一個題目姚手擼一個Promise,我說這有什麼難的,我來。 不曾想,實現加上寫文章整整花費了一個禮拜的業餘時間,著實有點難頂。在此厚臉皮的跟大家要個,你看成嗎?

面試官你來,130行帶你手寫完整Promise

我做完以後去網上查閱了一下,大家好像都用陣列對回撥方法進行儲存,如果我一開始也去看這一類的文章再自己動手,可能腦海裡也會習慣性的用已知方法解決。其實我想說就是,解決問題有時候不必一味的去模仿、照搬,靜下來自己想一想,也許你會有你自己的答案。

相關文章