手寫 Promise/A+ 實現,輕鬆透過 872 條用例
規範參考:Promise/A+ 規範 - 中文版本
測試工具:https://github.com/promises-aplus/promises-tests
前言
從接觸 Promise 到現在,筆者經歷了這麼個過程:
- 瞭解各種 Promise 規範,包括 Promise/A+,但對其具體內容不甚瞭解。
- 研究前人的 Promise 實現,自己嘗試編寫,但測試時經常遇到問題。
- 苦思冥想,為什麼返回值能夠決定 Promise 的狀態?為什麼鏈式呼叫要返回一個新的 Promise 而不是 this?我陷入了深深的困惑。
- 可能是由於執著,我深入研讀了 Promise/A+ 規範。儘管起初對一些條款感到困惑,但我依然堅持學習。
- 逐句翻譯、理解規範,逐漸熟悉了它。從最初需要對照規範編寫程式碼,到如今能夠手寫 Promise,這個過程充滿了樂趣,也讓我對 Promise 的一些常用方法有了更深的理解。
如果你也在學習 Promise,遇到困難,我建議你對照規範和程式碼來理解。
實現
在閱讀程式碼之前,你需要明白,程式碼中的註釋並非隨意新增,每條註釋都對應著 Promise/A+ 規範中的具體條款。
關於規範,它主要包括以下幾個部分:
- 專業術語,你可以簡單瞭解即可。
- 詳細規範,需要逐行理解,包括:
- 2.1 Promise 的 3 種狀態:pending(待定)、fulfilled(已實現)、rejected(已拒絕)。
- 2.2 Promise 的
then
方法的實現,不同狀態下應執行的操作。 - 2.3 Promise 對某值的決策行為,也稱為 Promise 解決過程。
因此,你會在註釋中看到類似以下的標記:
// 2.1 (2)(2)
它表示,規範對應的 2.1 Promise 狀態
下的第 2 個序號
下的 第 2 條內容
。
即 標題序號 內容序號...
。
下方程式碼中儘管沒有涵蓋所有規範條款的註釋,但是隱式實現了。
完整程式碼如下:
function MyPromise(executor) {
this.state = 'pending' // 2.1 (1)(1) 初始狀態,可以轉變為其它兩種狀態,也就是已實現(fulfilled)或已拒絕(rejected)
this.value = null // 2.1 (2)(2) 必須有一個不可改變的值
this.reason = null // 2.1 (3)(2) 必須有一個不可改變的原因
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
if (this.state !== 'pending') return
this.state = 'fulfilled'
this.value = value
// 2.2 (6)(1) 當 promise 被實現時,所有相應的 onFulfilled 回撥必須按它們呼叫 then 的順序執行
this.onFulfilledCallbacks.forEach((callback) => callback(this.value))
}
const reject = (reason) => {
if (this.state !== 'pending') return
this.state = 'rejected'
this.reason = reason
// 2.2 (6)(2) 當 promise 被拒絕時,所有相應的 onRejected 回撥必須按它們呼叫 then 的順序執行
this.onRejectedCallbacks.forEach((callback) => callback(this.reason))
}
// 如果 then 中對返回的 Promise 執行器做了異常處理,此步可選
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
// 2.2 (6) 原型上方法,根據不同狀態保證同一個 Promise 上的 then 可被多次呼叫
MyPromise.prototype.then = function (onFulfilled, onRejected) {
// 2.2 (1) onFulfilled 和 onRejected 引數可選,若不是函式則忽略(此處略微改造,本質上還是符合規範的,返回值或丟擲異常決策鏈呼叫)
const realOnFulfilled =
typeof onFulfilled === 'function'
? onFulfilled // 2.2 (2)
: (value) => {
return value
}
const realOnRejected =
typeof onRejected === 'function'
? onRejected // 2.2 (3)
: (reason) => {
throw reason
}
// 2.2 (7) then 必須返回一個 promise
let promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
// 2.2 (4) 必須在執行上下文棧僅包含平臺程式碼時才被呼叫
queueMicrotask(() => {
try {
// 2.2 (5) 必須作為函式呼叫
let x = realOnFulfilled(this.value)
// 2.2 (7)(1) 根據返回值 x 執行 Promise 解決過程 [[Resolve]](promise2, x),此處也內含了規範 2.2 (7)(3) 處理
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
// 2.2 (7)(2) 丟擲異常 e,則 promise2 必須以 e 作為原因被拒絕,此處也包含了 2.2 (7)(4) 的處理(非函式時上方預設函式丟擲 reason,這裡捕捉拒絕,不就是實現了嗎)
reject(e)
}
})
} else if (this.state === 'rejected') {
queueMicrotask(() => {
try {
let x = realOnRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
} else if (this.state === 'pending') {
// 如果 Promise 中的非同步執行在後,then新增在前,該步驟能保證回撥不被忽略,參考觀察者 or 釋出訂閱?
this.onFulfilledCallbacks.push(() => {
queueMicrotask(() => {
try {
let x = realOnFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
let x = realOnRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
// 2.2 (7)
return promise2
}
function resolvePromise(promise, x, resolve, reject) {
// 2.3 (1) 如果 promise 和 x 指向同一個物件,則以 TypeError 拒絕 promise 作為原因,這裡丟擲或者 return reject 均可
if (promise === x)
throw new TypeError('promise and return value are the same')
// 根據 Promise 或者 thenable 物件特性,可以直接判斷分支如下
if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
// 2.3 (3)(3)(3) 和 2.3 (3)(4)(1) 呼叫優先問題專用變數
let called = false
try {
const then = x.then
if (typeof then === 'function') {
// 2.3 (3)(3) 如果 then 是一個函式,則以 x 作為 this 呼叫它
then.call(
x,
(y) => {
if (called) return
called = true
// 2.3 (3)(3)(1)
resolvePromise(promise, y, resolve, reject)
},
(r) => {
if (called) return
called = true
// 2.3 (3)(3)(2)
reject(r)
}
)
} else {
// 2.3 (3)(4) x 為非 thenable 物件(如果 then 不是一個函式,則用 x 來實現 promise)
resolve(x)
}
} catch (e) {
if (called) return
called = true
// 2.3 (3)(2) 如果檢索屬性 x.then 時丟擲異常 e,則以 e 作為原因拒絕 promise
// 2.3 (3)(3)(4)(2) 如果呼叫 then 時丟擲異常,以 e 作為原因拒絕 promise
reject(e)
}
} else {
// 2.3 (4) 如果 x 既不是物件也不是函式,則用 x 來實現 promise
// x 為 null undefined、基本數值等情況
resolve(x)
}
}
// 暴露一個介面提供測試的靜態方法
MyPromise.deferred = function () {
const result = {}
result.promise = new MyPromise((resolve, reject) => {
result.resolve = resolve
result.reject = reject
})
return result
}
module.exports = MyPromise