首頁 > 軟體

萬字詳解JavaScript手寫一個Promise

2022-07-13 18:01:16

前言

手寫Promise現在已經成了面試的熱門內容,但在實際開發中基本都不會去手寫一個Promise,但是在面試中各種手寫題可能就會遇到一個手寫Promise,我們可以儘量提高我們的上限,從而獲取更多的工作機會。

Promise核心原理實現

首先我們從使用的角度來分析一下Promise,然後編寫一個最簡單版本的Promise。

Promise的使用分析

Promise就是一個類,在執行這個類的時候,需要傳遞一個執行器(回撥函數)進去,執行器會立即執行。

Promise中的狀態分為三個,分別是:

  • pending→等待
  • fulfilled→成功
  • rejected→失敗

狀態的切換隻有兩種,分別是:

  • pending→fulfilled
  • pending→rejected

一旦狀態發生改變,就不會再次改變:

  • 執行器中的兩個引數,分別是resolve和reject,其實就是兩個回撥函數,呼叫resolve是從pending狀態到fulfilled,呼叫reject是從狀態pending到rejected。傳遞給這兩個回撥函數的引數會作為成功或失敗的值。
  • Promise的範例物件具有一個then方法,該方法接受兩個回撥函數,分別來處理成功與失敗的狀態,then方法內部會進行判斷,然後根據當前狀態確定呼叫的回撥函數。then方法應該是被定義在原型物件中的。
  • then的回撥函數中都包含一個值,如果是成功,表示成功後返回的值;如果是失敗就表示失敗的原因。

MyPromise的實現

根據我們上面的分析,寫出如下程式碼:

MyPromise.js
// 定義所有狀態的常數
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// Promise實質上就是一個類,首先建立一個Promise的類
class MyPromise {
    // 範例化Promise時需要一個回撥函數,該回撥函數立即執行
    constructor(executor) {
        // 在呼叫executor需要傳遞兩個回撥函數,分別是resolve和reject
        executor(this.resolve, this.reject)
    }
    // Promise 的狀態
    status = PENDING
    // 記錄成功與失敗的值
    value = undefined
    reason = undefined
    resolve = (value) => {// 這裡使用箭頭函數是為了使其內部的this指向為該範例化後的物件
        // 形參value表示,呼叫resolve時傳遞的引數
        // 如果當前狀態不是pending,就直接跳出該邏輯
        if (this.status !== PENDING) return

        // 將狀態修改為成功
        this.status = FULFILLED

        // 將傳入的值進行儲存
        this.value = value
    }
    reject = (reason) => {// 這裡使用箭頭函數是為了使其內部的this指向為該範例化後的物件
        // 形參reason表示,呼叫reject時傳遞的失敗的原因
        // 如果當前狀態不是pending,就直接跳出該邏輯
        if (this.status !== PENDING) return

        // 將狀態修改為失敗
        this.status = REJECTED

        // 儲存失敗的原因
        this.reason = reason
    }
    // then方法的實現
    then (onFulfilled, onRejected) {
        // 判斷當前狀態,根據狀態呼叫指定回撥
        if (this.status === FULFILLED) {
            // 將成功的值作為引數返回
            onFulfilled(this.value)
        } else if (this.status === REJECTED) {
            // 將失敗的原因作為引數返回
            onRejected(this.reason)
        }
    }
}
// 匯出Promise
module.exports = MyPromise 

現在我們就來寫一段程式碼驗證一下上面的程式碼

驗證resolve:

const MyPromise = require('./myPromise')

let promise = new MyPromise((resolve, reject) => {
    resolve('成功')
})
promise.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})
/* 輸出
    成功
*/

驗證reject:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    reject('失敗')
})
promise.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})
/* 輸出
    失敗
*/

驗證狀態不可變:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    resolve('成功')
    reject('失敗')
})
promise.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})
/* 輸出
    成功
*/ 

在Promise中加入非同步操作

如果我們的程式碼中存在非同步操作,我們自己寫的Promise將毫無用處,

例如下面這段程式碼:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    setTimeout(resolve, 2000, '成功')
})
promise.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})

這段程式碼2000ms後沒有任何輸出,為了解決這個問題我們將對自己編寫的類進行如下操作:

  • 建立兩個實體方法用於儲存我們傳入的成功與失敗的處理邏輯。
  • then方法新增狀態為pending時的處理邏輯,這時將傳遞進來的屬性儲存到範例上。
  • 在成功或者失敗時呼叫相應的傳遞進來的回撥函數(範例屬性存在函數的情況下)。

實現程式碼如下:

// MyPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
    constructor(executor) {
        executor(this.resolve, this.reject)
    }
    status = PENDING
    value = undefined
    reason = undefined
    // 儲存成功與失敗的處理邏輯
    onFulfilled = undefined
    onRejected = undefined
    resolve = (value) => {
        if (this.status !== PENDING) return
        this.status = FULFILLED
        this.value = value
        // 如果將狀態修復為成功,呼叫成功的回撥
        this.onFulfilled && this.onFulfilled(this.value)
    }
    reject = (reason) => {
        if (this.status !== PENDING) return
        this.status = REJECTED
        this.reason = reason
        // 如果將狀態修復為失敗,呼叫失敗的回撥
        this.onRejected && this.onRejected(this.reason)
    }
    then (onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
            onFulfilled(this.value)
        } else if (this.status === REJECTED) {
            onRejected(this.reason)
        } else {
            // 表示既不是成功,也不是失敗。這個時候儲存傳遞進來的兩個回撥
            this.onFulfilled = onFulfilled
            this.onRejected = onRejected
        }
    }
}
module.exports = MyPromise

這裡的this.onFulfilled && this.onFulfilled(this.value)表示如果該屬性存在就呼叫這個方法。

現在我們重新執行一開始上面那一段程式碼,2s後會成功輸出成功

實現then方法的多次呼叫

Promise範例中存在要給then方法,允許我們在Promise範例中鏈式呼叫,每個then方法還會返回一個Promise範例,

如下圖所示:

Promise實體方法then是可以多次進行呼叫,而我們現在自己封裝的卻執行呼叫一次,現在根據新需要來重新改寫我們的程式碼,

實現思路如下:

  • 定義可以儲存多個回撥的陣列,用於儲存多個回撥函數。
  • 如果是同步執行的程式碼,執行後立即知道執行結果,所以可以直接呼叫回撥函數。
  • 如果是非同步程式碼,需要將每次回撥函數儲存到陣列中,然後狀態變化時依次呼叫函數。

實現程式碼如下:

// MyPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
    constructor(executor) {
        executor(this.resolve, this.reject)
    }
    status = PENDING
    value = undefined
    reason = undefined
    // 儲存成功與失敗的處理邏輯
    onFulfilled = []
    onRejected = []

    resolve = (value) => {
        if (this.status !== PENDING) return
        this.status = FULFILLED
        this.value = value

        // 如果將狀態修復為成功,呼叫成功的回撥
        while (this.onFulfilled.length) {
            // Array.prototype.shift() 用於刪除陣列第一個元素,並返回
            this.onFulfilled.shift()(this.value)
        }
    }
    reject = (reason) => {
        if (this.status !== PENDING) return
        this.status = REJECTED
        this.reason = reason
        // 如果將狀態修復為失敗,呼叫失敗的回撥
        while (this.onRejected.length) {
            // Array.prototype.shift() 用於刪除陣列第一個元素,並返回
            this.onRejected.shift()(this.reason)
        }
    }
    then (onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
            onFulfilled(this.value)
        } else if (this.status === REJECTED) {
            onRejected(this.reason)
        } else {
            // 表示既不是成功,也不是失敗。這個時候儲存傳遞進來的兩個回撥
            this.onFulfilled.push(onFulfilled)
            this.onRejected.push(onRejected)
        }
    }
}
module.exports = MyPromise

這裡我們通過陣列的shift()方法,該方法刪除陣列的第一個元素,並返回,返回的這個值正好是一個回撥函數,然後呼叫該函數即可實現該功能。

實現then的鏈式呼叫

想要實現then的鏈式呼叫,主要解決兩個問題:

  • 返回的是一個新的MyPormise範例。
  • then的返回值作為下一次的鏈式呼叫的引數。

這裡分為兩種情況:

  • 直接返回一個值,可以直接作為值使用
  • 返回一個新的MyPormise範例,此時就需要判斷其狀態

實現程式碼如下:

// MyPromise.js
/* 省略的程式碼同上 */
class MyPromise {
    /* 省略的程式碼同上 */

    // then方法的實現
    then (onFulfilled, onRejected) {
        // then 方法返回一個MyPromise範例
        return new MyPromise((resolve, reject) => {
            // 判斷當前狀態,根據狀態呼叫指定回撥
            if (this.status === FULFILLED) {
                // 將成功的值作為引數返回
                // 儲存執行回撥函數的結果
                const result = onFulfilled(this.value)

                // 如果返回的是一個普通的值,直接呼叫resolve
                // 如果是一個MyPromise範例,根據返回的解決來決定是呼叫resolve,還是reject
                resolvePromise(result, resolve, reject)
            } else if (this.status === REJECTED) {
                // 將失敗的原因作為引數返回
                onRejected(this.reason)
            } else {
                // 表示既不是成功,也不是失敗。這個時候儲存傳遞進來的兩個回撥
                this.onFulfilled.push(onFulfilled)
                this.onRejected.push(onRejected)
            }
        })
    }
}
function resolvePromise (result, resolve, reject) {
    // 判斷傳遞的result是不是MyPromise的範例物件
    if (result instanceof MyPromise) {
        // 說明是MyPromise的範例物件
        // 呼叫.then方法,然後在回撥函數中獲取到具體的值,然後呼叫具體的回撥
        // result.then(value => resolve(value), reason => reject(reason))
        // 簡寫
        result.then(resolve, reject)
    } else {
        resolve(result)
    }
}
module.exports = MyPromise

then方法鏈式呼叫識別Promise物件自返回

在Promise中,如果then方法返回的是自己的promise物件,則會發生promise的巢狀,這個時候程式會報錯,

測試程式碼如下:

var promise = new Promise((resolve, reject) => {
  resolve(100)
})
var p1 = promise.then(value => {
  console.log(value)
  return p1
})
// 100
// Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

想要解決這個問題其實我們只需要在then方法返回的MyPromise範例物件與then中回撥函數返回的值進行比對,如果相同的返回一個reject的MyPromise範例物件,並建立一個TypeError型別的Error。

實現程式碼如下:

// MyPromise.js
    /* 省略的程式碼同上 */
    then (onFulfilled, onRejected) {
        // then 方法返回一個MyPromise範例
        const promise = new MyPromise((resolve, reject) => {
            // 判斷當前狀態,根據狀態呼叫指定回撥
            if (this.status === FULFILLED) {
                // 這裡並不需要延遲執行,而是通過setTimeout將其變成非同步函數
                // 如果不變成非同步的話是在函數內獲取不到promise的
                setTimeout(() => {
                    // 將成功的值作為引數返回
                    // 儲存執行回撥函數的結果
                    const result = onFulfilled(this.value)

                    // 如果返回的是一個普通的值,直接呼叫resolve
                    // 如果是一個MyPromise範例,根據返回的解決來決定是呼叫resolve,還是reject
                    resolvePromise(promise, result, resolve, reject)
                }, 0)
            } else if (this.status === REJECTED) {
                // 將失敗的原因作為引數返回
                onRejected(this.reason)
            } else {
                // 表示既不是成功,也不是失敗。這個時候儲存傳遞進來的兩個回撥
                this.onFulfilled.push(onFulfilled)
                this.onRejected.push(onRejected)
            }
        })
        return promise
    }
}
function resolvePromise (promise, result, resolve, reject) {
    // 這裡修改一下該函數,如果return的Promise範例物件,也就是傳入的promise===result的話,說明在promise中return的是當前promise物件。
    if (promise === result) {
        // 這裡呼叫reject,並丟擲一個Error
        // return 是必須的,阻止程式向下執行
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    // 判斷傳遞的result是不是MyPromise的範例物件
    if (result instanceof MyPromise) {
        // 說明是MyPromise的範例物件
        // 呼叫.then方法,然後在回撥函數中獲取到具體的值,然後呼叫具體的回撥
        // result.then(value => resolve(value), reason => reject(reason))
        // 簡寫
        result.then(resolve, reject)
    } else {
        resolve(result)
    }
}
module.exports = MyPromise

這裡then方法中的setTimeout的作用並不是延遲執行,而是為了呼叫resolvePromise函數時,保證建立的promise存在。

捕獲錯誤及 then 鏈式呼叫其他狀態程式碼補充

到目前為止我們現實的Promise並沒有對異常做任何處理,為了保證程式碼的健壯性,我們需要對異常做一些處理。

捕獲執行器錯誤

現在我們需要對執行器進行異常捕獲,如果發生異常,就將我們的狀態修改為rejected

捕獲執行器的錯誤也比較簡單,只需要在建構函式中加入try...catch語句就可以,

實現程式碼如下:

    constructor(executor) {
        try {
            // 在呼叫executor需要傳遞兩個回撥函數,分別是resolve和reject
            executor(this.resolve, this.reject)
        } catch (e) {
            // 發生異常呼叫reject
            this.reject(e)
        }
    }

測試程式碼如下:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    throw new Error('執行器錯誤')
})

promise.then(value => {
    console.log(value);
}, error => {
    console.log(error.message);
})
/* 輸出
    執行器錯誤
*/

捕獲then中的報錯

現在我們需要對then中的異常捕獲到,並在下一次鏈式呼叫中傳遞到then的第二個函數中,實現的方式也是通過try...catch語句,

範例程式碼如下:

// then方法的實現
then (onFulfilled, onRejected) {
    // then 方法返回一個MyPromise範例
    const promise = new MyPromise((resolve, reject) => {
        // 判斷當前狀態,根據狀態呼叫指定回撥
        if (this.status === FULFILLED) {
            // 這裡並不需要延遲執行,而是通過setTimeout將其變成非同步函數
            // 如果不變成非同步的話是在函數內獲取不到promise的
            setTimeout(() => {
                try {
                    // 將成功的值作為引數返回
                    // 儲存執行回撥函數的結果
                    const result = onFulfilled(this.value)

                    // 如果返回的是一個普通的值,直接呼叫resolve
                    // 如果是一個MyPromise範例,根據返回的解決來決定是呼叫resolve,還是reject
                    resolvePromise(promise, result, resolve, reject)
                } catch (error) {
                    reject(error)
                }
            }, 0)
        } else if (this.status === REJECTED) {
            // 將失敗的原因作為引數返回
            onRejected(this.reason)
        } else {
            // 表示既不是成功,也不是失敗。這個時候儲存傳遞進來的兩個回撥
            this.onFulfilled.push(onFulfilled)
            this.onRejected.push(onRejected)
        }
    })
    return promise
}

測試程式碼如下:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    resolve('成功')
})
// 第一個then方法中的錯誤要在第二個then方法中捕獲到
promise.then(value => {
    console.log('resolve', value)
    throw new Error('then的執行過程中遇到異常')
}).then(null, reason => {
    console.log(reason.message)
})

/* 輸出
    resolve 成功
    then的執行過程中遇到異常
*/

錯誤與非同步狀態的鏈式呼叫

現在只對成功狀態的then進行的鏈式呼叫以及錯誤處理,錯誤與非同步狀態未進行處理,其實處理起來也是一樣的,

範例程式碼如下:

    // then方法的實現
    then (onFulfilled, onRejected) {
        // then 方法返回一個MyPromise範例
        const promise = new MyPromise((resolve, reject) => {
            // 判斷當前狀態,根據狀態呼叫指定回撥
            if (this.status === FULFILLED) {
                // 這裡並不需要延遲執行,而是通過setTimeout將其變成非同步函數
                // 如果不變成非同步的話是在函數內獲取不到promise的
                setTimeout(() => {
                    try {
                        // 將成功的值作為引數返回
                        // 儲存執行回撥函數的結果
                        const result = onFulfilled(this.value)

                        // 如果返回的是一個普通的值,直接呼叫resolve
                        // 如果是一個MyPromise範例,根據返回的解決來決定是呼叫resolve,還是reject
                        resolvePromise(promise, result, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0)
            } else if (this.status === REJECTED) {
                // 失敗的處理同成功處理,只是呼叫的回撥函數不同
                setTimeout(() => {
                    try {
                        const result = onRejected(this.reason)
                        resolvePromise(promise, result, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0)
            } else {
                this.onFulfilled.push((value) => {
                    setTimeout(() => {
                        try {
                            const result = onFulfilled(value)
                            resolvePromise(promise, result, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0)
                })
                this.onRejected.push((reason) => {
                    setTimeout(() => {
                        try {
                            const result = onRejected(reason)
                            resolvePromise(promise, result, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0)
                })
            }
        })
        return promise
    }

測試程式碼如下:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    setTimeout(resolve, 2000, '成功')
})
// 第一個then方法中的錯誤要在第二個then方法中捕獲到
promise.then(value => {
    console.log('resolve', value)
    throw new Error('then的執行過程中遇到異常')
}).then(null, reason => {
    console.log(reason.message)
})
/* 輸出
    resolve 成功
    then的執行過程中遇到異常
*/

將then方法的引數變成可選引數

Promise中的then方法其實是兩個可以可選引數,如果我們不傳遞任何引數的話,裡面的結果是向下傳遞的,直到捕獲為止,

例如下面這段程式碼:

new Promise((resolve, reject) => {
    resolve(100)
})
    .then()
    .then()
    .then()
    .then(value => console.log(value))
// 最後一個then輸入100 

這段程式碼可以理解為:

new Promise((resolve, reject) => {
    resolve(100)
})
    .then(value => value)
    .then(value => value)
    .then(value => value)
    .then(value => console.log(value))

所以說我們只需要在沒有傳遞迴撥函數時,賦值一個預設的回撥函數即可。

實現程式碼如下:

// then方法的實現
then (onFulfilled, onRejected) {
    // 如果傳遞函數,就是用傳遞的函數,否則指定一個預設值,用於引數傳遞
    onFulfilled = onFulfilled ? onFulfilled : value => value
    // 同理
    onRejected = onRejected ? onRejected : reason => { throw reason }
    // then 方法返回一個MyPromise範例
    const promise = new MyPromise((resolve, reject) => {
        // 判斷當前狀態,根據狀態呼叫指定回撥
        if (this.status === FULFILLED) {...
        } else if (this.status === REJECTED) {...
        } else {...
        }
    })
    return promise
}

Promise.all方法的實現

關於all()方法的使用,可以引數Promise.all()。簡單的說Promise.all()會將多個Promise範例包裝為一個Promise範例,且順序與呼叫順序一致,

範例程式碼如下:

function p1 () {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('p1')
        }, 2000)
    })
}
function p2 () {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('p2')
        }, 0)
    })
}
Promise.all(['a', 'b', p1(), p2(), 'c']).then(result => {
    console.log(result)
    // ["a", "b", "p1", "p2", "c"]
})

在這段程式碼中,我們的p1的執行是延遲了2s的,這裡如果不使用Promise.all()的話最終順序是與我們呼叫不同的。

現在我們來分析一下all()的實現思路:

  • all()方法可以通過類直接呼叫,所以是一個靜態方法
  • all()方法接收一個陣列,陣列中的值可以是一個普通值,也可以是一個MyPromise的範例物件
  • return一個新的MyPromise範例物件
  • 遍歷陣列中的每一個值,判斷值得型別,如果是一個普通值得話直接將值存入一個陣列;如果是一個MyPromise的範例物件的話,會呼叫then方法,然後根據執行後的狀態,如果失敗的話呼叫新的MyPromise範例物件中的rejecte,如果是成功話將這個值存入一個陣列
  • 存入陣列時計數,如果存入的數量達到傳入的陣列長度,說明呼叫完畢,執行resolve並將最終的結果陣列作為引數返回。

實現程式碼:

/** 
 * @description: 將多個Promise範例合併為一個Promise範例
 * @param {*} array Promise或者普通值
 * @returns {Promise}
 */
static all (array) {
    // 用於存放最終結果的陣列
    let result = []
    // 用於計算當前已經執行完的範例的數量
    let count = 0
    // 最後返回的是一個Promise範例
    return new MyPromise((resolve, reject) => {
        /** 
         * @description: 將執行完畢的值加入結果陣列,並根據實際情況決定是否呼叫resolve
         * @param {*} result 存放結果的陣列
         * @param {*} index 要加入的索引
         * @param {*} value 要加入陣列的值
         * @param {*} resolve Promise中的resolve
         */
        function addResult (result, index, value, resolve) {
            // 根據索引值,將結果堆入陣列中
            result[index] = value
            // 執行完畢一個 count+1,如果當前值等於總長度的話說明已經執行結束了,可以直接呼叫resolve,說明已經成功執行完畢了
            if (++count === array.length) {
                // 將執行結果返回
                resolve(result)
            }
        }
        // 遍歷穿入的陣列,每個都執行then方法,獲取到最終的結果
        array.forEach((p, index) => {
            // 判斷p是不是MyPromise的範例,如果是的話呼叫then方法,不是直接將值加入陣列中
            if (p instanceof MyPromise) {
                p.then(
                    // 成功時將結果直接加入陣列中
                    value => {
                        addResult(result, index, value, resolve)
                    },
                    // 如果失敗直接返回失敗原因
                    reason => {
                        reject(reason)
                    }
                )
            }
            else {
                addResult(result, index, p, resolve)
            }
        })
    })
}

Promise.resolve方法的實現

關於Promise.resolve()方法的用法可以參考Promise.resolve()Promise.reject()

我們實現的思路主要如下:

  • 該方法是一個靜態方法
  • 該方法接受的如果是一個值就將該值包裝為一個MyPromise的範例物件返回,如果是一個MyPromise的範例物件,呼叫then方法返回。

實現程式碼如下:

static resolve (value) {
    // 如果是MyPromise的範例,就直接返回這個範例
    if (value instanceof MyPromise) return value
    // 如果不是的話建立一個MyPromise範例,並返回傳遞的值
    return new MyPromise((resolve) => {
        resolve(value)
    })
}

finally方法的實現

關於finally方法可參考finally()實現該方法的實現程式碼如下:

finally (callback) {
    // 如何拿到當前的promise的狀態,使用then方法,而且不管怎樣都返回callback
    // 而且then方法就是返回一個promise物件,那麼我們直接返回then方法呼叫之後的結果即可
    // 我們需要在回撥之後拿到成功的回撥,所以需要把value也return
    // 失敗的回撥也丟擲原因
    // 如果callback是一個非同步的promise物件,我們還需要等待其執行完畢,所以需要用到靜態方法resolve
    return this.then(value => {
        // 把callback呼叫之後返回的promise傳遞過去,並且執行promise,且在成功之後返回value
        return MyPromise.resolve(callback()).then(() => value)
    }, reason => {
        // 失敗之後呼叫的then方法,然後把失敗的原因返回出去。
        return MyPromise.resolve(callback()).then(() => { throw reason })
    })
}

測試:

function p1 () {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve('p1')
        }, 2000)
    })
}
function p2 () {
    return new MyPromise((resolve, reject) => {
        reject('p2 reject')
    })
}
p2().finally(
    () => {
        console.log('finally p2')
        return p1()
    }
).then(
    value => {
        console.log(value)
    }, reason => {
        console.log(reason)
    }
)
// finally p2
// 兩秒之後執行p2 reject

catch方法的實現

關於catch方法可以參考catch(),實現該方法其實非常簡單,只需要在內部呼叫then方法,不傳遞第一個回撥函數即可,

實現程式碼如下:

catch (callback) {
    return this.then(null, failCallback)
}

測試如下:

function p () {
    return new MyPromise((resolve, reject) => {
        reject(new Error('reject'))
    })
}
p()
    .then(value => {
        console.log(value)
    })
    .catch(reason => console.log(reason))

完整程式碼

// MyPromise.js
// 定義所有狀態的常數
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// Promise實質上就是一個類,首先建立一個Promise的類
class MyPromise {
    // 範例化Promise時需要一個回撥函數,該回撥函數立即執行
    constructor(executor) {
        try {
            // 在呼叫executor需要傳遞兩個回撥函數,分別是resolve和reject
            executor(this.resolve, this.reject)
        } catch (e) {
            // 發生異常呼叫reject
            this.reject(e)
        }
    }
    // Promise 的狀態
    status = PENDING
    // 記錄成功與失敗的值
    value = undefined
    reason = undefined
    // 儲存成功與失敗的處理邏輯
    onFulfilled = []
    onRejected = []

    resolve = (value) => {// 這裡使用箭頭函數是為了使其內部的this指向為該範例化後的物件
        // 形參value表示,呼叫resolve時傳遞的引數
        // 如果當前狀態不是pending,就直接跳出該邏輯
        if (this.status !== PENDING) return
        // 將狀態修改為成功
        this.status = FULFILLED
        // 將傳入的值進行儲存
        this.value = value
        // 如果將狀態修復為成功,呼叫成功的回撥
        // this.onFulfilled && this.onFulfilled(this.value)
        while (this.onFulfilled.length) {
            // Array.prototype.shift() 用於刪除陣列第一個元素,並返回
            this.onFulfilled.shift()(this.value)
        }
    }
    reject = (reason) => {// 這裡使用箭頭函數是為了使其內部的this指向為該範例化後的物件
        // 形參reason表示,呼叫reject時傳遞的失敗的原因
        // 如果當前狀態不是pending,就直接跳出該邏輯
        if (this.status !== PENDING) return

        // 將狀態修改為失敗
        this.status = REJECTED

        // 儲存失敗的原因
        this.reason = reason

        // 如果將狀態修復為失敗,呼叫失敗的回撥
        // this.onRejected && this.onRejected(this.reason)
        while (this.onRejected.length) {
            // Array.prototype.shift() 用於刪除陣列第一個元素,並返回
            this.onRejected.shift()(this.reason)
        }
    }
    // then方法的實現
    then (onFulfilled, onRejected) {
        // 如果傳遞函數,就是用傳遞的函數,否則指定一個預設值,用於引數傳遞
        onFulfilled = onFulfilled ? onFulfilled : value => value
        // 同理
        onRejected = onRejected ? onRejected : reason => { throw reason }
        // then 方法返回一個MyPromise範例
        const promise = new MyPromise((resolve, reject) => {
            // 判斷當前狀態,根據狀態呼叫指定回撥
            if (this.status === FULFILLED) {
                // 這裡並不需要延遲執行,而是通過setTimeout將其變成非同步函數
                // 如果不變成非同步的話是在函數內獲取不到promise的
                setTimeout(() => {
                    try {
                        // 將成功的值作為引數返回
                        // 儲存執行回撥函數的結果
                        const result = onFulfilled(this.value)

                        // 如果返回的是一個普通的值,直接呼叫resolve
                        // 如果是一個MyPromise範例,根據返回的解決來決定是呼叫resolve,還是reject
                        resolvePromise(promise, result, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0)
            } else if (this.status === REJECTED) {
                // 失敗的處理同成功處理,只是呼叫的回撥函數不同
                setTimeout(() => {
                    try {
                        const result = onRejected(this.reason)
                        resolvePromise(promise, result, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0)
            } else {
                // 表示既不是成功,也不是失敗。這個時候儲存傳遞進來的兩個回撥
                // this.onFulfilled.push(onFulfilled)
                // this.onRejected.push(onRejected)
                this.onFulfilled.push((value) => {
                    setTimeout(() => {
                        try {
                            const result = onFulfilled(value)
                            resolvePromise(promise, result, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0)
                })
                this.onRejected.push((reason) => {
                    setTimeout(() => {
                        try {
                            const result = onRejected(reason)
                            resolvePromise(promise, result, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0)
                })
            }
        })
        return promise
    }
    catch (callback) {
        return this.then(null, callback)
    }
    finally (callback) {
        // 如何拿到當前的promise的狀態,使用then方法,而且不管怎樣都返回callback
        // 而且then方法就是返回一個promise物件,那麼我們直接返回then方法呼叫之後的結果即可
        // 我們需要在回撥之後拿到成功的回撥,所以需要把value也return
        // 失敗的回撥也丟擲原因
        // 如果callback是一個非同步的promise物件,我們還需要等待其執行完畢,所以需要用到靜態方法resolve
        return this.then(value => {
            // 把callback呼叫之後返回的promise傳遞過去,並且執行promise,且在成功之後返回value
            return MyPromise.resolve(callback()).then(() => value)
        }, reason => {
            // 失敗之後呼叫的then方法,然後把失敗的原因返回出去。
            return MyPromise.resolve(callback()).then(() => { throw reason })
        })
    }
    /** 
     * @description: 將多個Promise範例合併為一個Promise範例
     * @param {*} array Promise或者普通值
     * @returns {Promise}
     */
    static all (array) {
        // 用於存放最終結果的陣列
        let result = []
        // 用於計算當前已經執行完的範例的數量
        let count = 0
        // 最後返回的是一個Promise範例
        return new MyPromise((resolve, reject) => {
            /** 
             * @description: 將執行完畢的值加入結果陣列,並根據實際情況決定是否呼叫resolve
             * @param {*} result 存放結果的陣列
             * @param {*} index 要加入的索引
             * @param {*} value 要加入陣列的值
             * @param {*} resolve Promise中的resolve
             */
            function addResult (result, index, value, resolve) {
                // 根據索引值,將結果堆入陣列中
                result[index] = value
                // 執行完畢一個 count+1,如果當前值等於總長度的話說明已經執行結束了,可以直接呼叫resolve,說明已經成功執行完畢了
                if (++count === array.length) {
                    // 將執行結果返回
                    resolve(result)
                }
            }
            // 遍歷穿入的陣列,每個都執行then方法,獲取到最終的結果
            array.forEach((p, index) => {
                // 判斷p是不是MyPromise的範例,如果是的話呼叫then方法,不是直接將值加入陣列中
                if (p instanceof MyPromise) {
                    p.then(
                        // 成功時將結果直接加入陣列中
                        value => {
                            addResult(result, index, value, resolve)
                        },
                        // 如果失敗直接返回失敗原因
                        reason => {
                            reject(reason)
                        }
                    )
                }
                else {
                    addResult(result, index, p, resolve)
                }
            })
        })
    }
    static resolve (value) {
        // 如果是MyPromise的範例,就直接返回這個範例
        if (value instanceof MyPromise) return value
        // 如果不是的話建立一個MyPromise範例,並返回傳遞的值
        return new MyPromise((resolve) => {
            resolve(value)
        })
    }

}
function resolvePromise (promise, result, resolve, reject) {
    // 這裡修改一下該函數,如果return的Promise範例物件,也就是傳入的promise===result的話,說明在promise中return的是當前promise物件。
    if (promise === result) {
        // 這裡呼叫reject,並丟擲一個Error
        // return 是必須的,阻止程式向下執行
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    // 判斷傳遞的result是不是MyPromise的範例物件
    if (result instanceof MyPromise) {
        // 說明是MyPromise的範例物件
        // 呼叫.then方法,然後在回撥函數中獲取到具體的值,然後呼叫具體的回撥
        // result.then(value => resolve(value), reason => reject(reason))
        // 簡寫
        result.then(resolve, reject)
    } else {
        resolve(result)
    }
}
module.exports = MyPromise

到此這篇關於萬字詳解JavaScript手寫一個Promise的文章就介紹到這了,更多相關JS手寫Promise內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com