0. 前言
面試官:「你寫個 Promise 吧。」
我:「對不起,打擾了,再見!」
現在前端越來越卷,不會手寫 Promise
都不好意思面試了(手動狗頭.jpg)。雖然沒多少人會在業務中用自己實現的 Promise
,但是,實現 Promise
的過程會讓你對 Promise
更加了解,出了問題也可以更好地排查。
如果你還不熟悉 Promise,建議先看一下 MDN 文件。
在實現 Promise 之前,我建議你先看一遍 Promises/A+ 規範(中文翻譯:Promise A+ 規範),本文中不會再次介紹相關的概念。推薦大家優先閱讀原版英文,只需高中水平的英語知識就夠了,遇到不懂的再看譯文。
另外,本文將使用 ES6 中的 Class 來實現 Promise
。為了方便大家跟 Promise/A+ 規範對照著看,下文的順序將按照規範的順序來行文。
在正式開始之前,我們新建一個專案,名稱隨意,按照以下步驟進行初始:
- 開啟 CMD 或 VS Code,執行
npm init
,初始化專案 - 新建
PromiseImpl.js
檔案,後面所有的程式碼實現都寫在這個檔案裡
完整程式碼地址:ashengtan/promise-aplus-implementing
1. 術語
這部分大家直接看規範就好,也沒什麼好解釋的。注意其中對於 value
的描述,value
可以是一個 thenable
(有 then
方法的物件或函式) 或者 Promise
,這點會在後面的實現中體現出來。
2. 要求
2.1 Promise 的狀態
一個 Promise
有三種狀態:
pending
:初始狀態fulfilled
:成功執行rejected
:拒絕執行
一個 Promise
一旦從 pending
變為 fulfilled
或 rejected
,就無法變成其他狀態。當 fulfilled
時,需要給出一個不可變的值;同樣,當 rejected
時,需要給出一個不可變的原因。
根據以上資訊,我們定義 3 個常量,用來表示 Promise
的狀態:
const STATUS_PENDING = 'pending'
const STATUS_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'
接著,我們先把 Promise
的基礎框架先定義出來,這裡我使用 ES6 的 Class 來定義:
class PromiseImpl {
constructor() {}
then(onFulfilled, onRejected) {}
}
這裡我們先回想一下 Promise
的基本用法:
const promise = new Promise((resolve, reject) => {
// ...do something
resolve(value) // or reject(error)
})
// 多次呼叫
const p1 = promise.then()
const p2 = promise.then()
const p3 = promise.then()
好了,繼續完善 PromiseImpl
,先完善一下構造方法:
class PromiseImpl {
constructor() {
// `Promise` 當前的狀態,初始化時為 `pending`
this.status = STATUS_PENDING
// fulfilled 時的值
this.value = null
// rejected 時的原因
this.reason = null
}
}
另外,我們還要定義兩個方法,用於 fulfilled
和 rejected
時回撥:
class PromiseImpl {
constructor() {
// ...其他程式碼
// 2.1.2 When `fulfilled`, a `promise`:
// 2.1.2.1 must not transition to any other state.
// 2.1.2.2 must have a value, which must not change.
const _resolve = value => {
// 如果 `value` 是 `Promise`(即巢狀 `Promise`),
// 則需要等待該 `Promise` 執行完成
if (value instanceof PromiseImpl) {
return value.then(
value => _resolve(value),
reason => _reject(reason)
)
}
if (this.status === STATUS_PENDING) {
this.status = STATUS_FULFILLED
this.value = value
}
}
// 2.1.3 When `rejected`, a `promise`:
// 2.1.3.1 must not transition to any other state.
// 2.1.3.2 must have a reason, which must not change.
const _reject = reason => {
if (this.status === STATUS_PENDING) {
this.status = STATUS_REJECTED
this.reason = reason
}
}
}
}
注意,在 _resolve()
中,如果 value
是 Promise
的話(即巢狀 Promise
),則需要等待該 Promise
執行完成。這點很重要,因為後面的其他 API 如 Promise.resolve
、Promise.all
、Promise.allSettled
等均需要等待巢狀 Promise
執行完成才會返回結果。
最後,別忘了在 new Promise()
時,我們需要將 resolve
和 reject
傳給呼叫者:
class PromiseImpl {
constructor(executor) {
// ...其他程式碼
try {
executor(_resolve, _reject)
} catch (e) {
_reject(e)
}
}
}
使用 trycatch
將 executor
包裹起來,因為這部分是呼叫者的程式碼,我們無法保證呼叫者的程式碼不會出錯。
2.2 Then 方法
一個 Promise
必須提供一個 then
方法,其接受兩個引數:
promise.then(onFulfilled, onRejected)
class PromiseImpl {
then(onFulfilled, onRejected) {}
}
2.2.1 onFulfilled
和 onRejected
從規範 2.2.1 中我們可以得知以下資訊:
onFulfilled
和onRejected
是可選引數- 如果
onFulfilled
和onRejected
不是函式,則必須被忽略
因此,我們可以這樣實現:
class PromiseImpl {
then(onFulfilled, onRejected) {
// 2.2.1 Both `onFulfilled` and `onRejected` are optional arguments:
// 2.2.1.1 If `onFulfilled` is not a function, it must be ignored
// 2.2.1.2 If `onRejected` is not a function, it must be ignored
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {}
onRejected = typeof onRejected === 'function' ? onRejected : () => {}
}
}
2.2.2 onFulfilled
特性
從規範 2.2.2 中我們可以得知以下資訊:
- 如果
onFulfilled
是一個函式,則必須在fulfilled
後呼叫,第一個引數為promise
的值 - 只能呼叫一次
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他程式碼
// 2.2.2 If `onFulfilled` is a function:
// 2.2.2.1 it must be called after `promise` is fulfilled,
// with promise’s value as its first argument.
// 2.2.2.2 it must not be called before `promise` is fulfilled.
// 2.2.2.3 it must not be called more than once.
if (this.status === STATUS_FULFILLED) {
onFulfilled(this.value)
}
}
}
2.2.3 onRejected
特性
與 onFulfilled
同理,只不過是在 rejected
時呼叫,第一個引數為 promise
失敗的原因
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他程式碼
// 2.2.3 If onRejected is a function:
// 2.2.3.1 it must be called after promise is rejected,
// with promise’s reason as its first argument.
// 2.2.3.2 it must not be called before promise is rejected.
// 2.2.3.3 it must not be called more than once.
if (this.status === STATUS_REJECTED) {
onRejected(this.reason)
}
}
}
2.2.4 非同步執行
在日常開發中,我們經常使用 Promise
來做一些非同步操作,規範 2.2.4 就是規定非同步執行的問題,具體的可以結合規範裡的註釋閱讀,重點是確保 onFulfilled
和 onRejected
要非同步執行。
需要指出的是,規範裡並沒有規定 Promise
一定要用 micro-task
機制來實現,因此你使用 macro-task
機制來實現也是可以的。當然,現在瀏覽器用的是 micro-task
來實現。這裡為了方便,我們使用 setTimeout
(屬於 macro-task
)來實現。
因此,我們需要稍微改造下上面的程式碼:
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他程式碼
// fulfilled
if (this.status === STATUS_FULFILLED) {
setTimeout(() => {
onFulfilled(this.value)
}, 0)
}
// rejected
if (this.status === STATUS_REJECTED) {
setTimeout(() => {
onRejected(this.reason)
}, 0)
}
}
}
2.2.5 onFulfilled
和 onRejected
必須作為函式被呼叫
這個已經在上面實現過了。
2.2.6 then
可被多次呼叫
舉個例子:
const promise = new Promise((resolve, reject) => {
// ...do something
resolve(value) // or reject(error)
})
promise.then()
promise.then()
promise.catch()
因此,必須確保當 Promise
fulfilled
或 rejected
時,onFulfilled
或 onRejected
按照其註冊的順序逐一回撥。還記得最開始我們定義的 resolve
和 reject
嗎?這裡我們需要改造下,保證所有的回撥都被執行到:
const invokeArrayFns = (fns, arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg)
}
}
class PromiseImpl {
constructor(executor) {
// ...其他程式碼
// 用於存放 `fulfilled` 時的回撥,一個 `Promise` 物件可以註冊多個 `fulfilled` 回撥函式
this.onFulfilledCbs = []
// 用於存放 `rejected` 時的回撥,一個 `Promise` 物件可以註冊多個 `rejected` 回撥函式
this.onRejectedCbs = []
const resolve = value => {
if (this.status === STATUS_PENDING) {
this.status = STATUS_FULFILLED
this.value = value
// 2.2.6.1 If/when `promise` is fulfilled,
// all respective `onFulfilled` callbacks must execute
// in the order of their originating calls to `then`.
invokeArrayFns(this.onFulfilledCbs, value)
}
}
const reject = reason => {
if (this.status === STATUS_PENDING) {
this.status = STATUS_REJECTED
this.reason = reason
// 2.2.6.2 If/when `promise` is rejected,
// all respective `onRejected` callbacks must execute
// in the order of their originating calls to `then`.
invokeArrayFns(this.onRejectedCbs, reason)
}
}
}
}
看到這裡你可能會有疑問,什麼時候往 onFulfilledCbs
和 onRejectedCbs
裡存放對應的回撥,答案是在呼叫 then
時:
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他程式碼
// pending
if (this.status === STATUS_PENDING) {
this.onFulfilledCbs.push(() => {
setTimeout(() => {
onFulfilled(this.value)
}, 0)
})
this.onRejectedCbs.push(() => {
setTimeout(() => {
onRejected(this.reason)
}, 0)
})
}
}
}
此時 Promise
處於 pending
狀態,無法確定其最後是 fulfilled
還是 rejected
,因此需要將回撥函式存放起來,待狀態確定後再執行相應的回撥函式。
注:invokeArrayFns
來源於 Vue.js 3 中的原始碼。
2.2.7 then
必須返回 Promise
promise2 = promise1.then(onFulfilled, onRejected)
那麼,在我們上面的程式碼中,then
怎麼才能返回 Promise
呢?很簡單:
class PromiseImpl {
then(onFulfilled, onRejected) {
let promise2 = new PromiseImpl((resolve, reject) => {
if (this.status === STATUS_FULFILLED) {
// ...相關程式碼
}
if (this.status === STATUS_REJECTED) {
// ...相關程式碼
}
if (this.status === STATUS_PENDING) {
// ...相關程式碼
}
})
return promise2
}
}
因為呼叫 then
之後返回一個新的 Promise
物件,使得我們也可以進行鏈式呼叫:
Promise.resolve(42).then().then()...
2.2.7.1 ~ 2.2.7.4 這四點比較重要,我們下面分別來看看。
2.2.7.1 如果 onFulfilled
或 onRejected
返回一個值 x
,則執行 Promise
解決過程,[[Resolve]](promise2, x)
。
解釋:其實所謂執行 Promise
解決過程就是執行某個操作,我們把這個操作抽取成一個方法,並命名為:promiseResolutionProcedure(promise, x, resolve, reject)
。為了方便,我們把 resolve
和 reject
一併透傳進去。
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他程式碼
let promise2 = new PromiseImpl((resolve, reject) => {
if (this.status === STATUS_FULFILLED) {
setTimeout(() => {
// 2.2.7.1
let x = onFulfilled(this.value)
promiseResolutionProcedure(promise2, x, resolve, reject)
}, 0)
}
if (this.status === STATUS_REJECTED) {
setTimeout(() => {
// 2.2.7.1
let x = onRejected(this.reason)
promiseResolutionProcedure(promise2, x, resolve, reject)
}, 0)
}
if (this.status === STATUS_PENDING) {
this.onFulfilledCbs.push(() => {
setTimeout(() => {
// 2.2.7.1
let x = onFulfilled(this.value)
promiseResolutionProcedure(promise2, x, resolve, reject)
}, 0)
})
this.onRejectedCbs.push(() => {
setTimeout(() => {
// 2.2.7.1
let x = onRejected(this.reason)
promiseResolutionProcedure(promise2, x, resolve, reject)
}, 0)
})
}
})
return promise2
}
}
2.2.7.2 如果 onFulfilled
或 onRejected
丟擲一個異常 e
,則 promise2
必須 rejected
,並返回原因 e
。
解釋:實現上面體現在執行 onFulfilled
或 onRejected
時使用 trycatch
包括起來,並在 catch
時呼叫 reject(e)
。
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他程式碼
let promise2 = new PromiseImpl((resolve, reject) => {
if (this.status === STATUS_FULFILLED) {
setTimeout(() => {
try {
// 2.2.7.1
let x = onFulfilled(this.value)
promiseResolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
// 2.2.7.2
reject(e)
}
}, 0)
}
if (this.status === STATUS_REJECTED) {
setTimeout(() => {
try {
// 2.2.7.1
let x = onRejected(this.reason)
promiseResolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
// 2.2.7.2
reject(e)
}
}, 0)
}
if (this.status === STATUS_PENDING) {
this.onFulfilledCbs.push(() => {
setTimeout(() => {
try {
// 2.2.7.1
let x = onFulfilled(this.value)
promiseResolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
// 2.2.7.2
reject(e)
}
}, 0)
})
this.onRejectedCbs.push(() => {
setTimeout(() => {
try {
// 2.2.7.1
let x = onRejected(this.reason)
promiseResolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
// 2.2.7.2
reject(e)
}
}, 0)
})
}
})
// 2.2.7 `then` must return a promise
return promise2
}
}
2.2.7.3 如果 onFulfilled
不是函式且 promise1
已經 fulfilled
,則 promise2
必須 fulfilled
且返回與 promise1
相同的值。
解釋:值的透傳,例如:
Promise.resolve(42).then().then(value => console.log(value)) // 42
在第一個 then
時,我們忽略了 onFulfilled
,那麼在鏈式呼叫的時候,需要把值透傳給後面的 then
:
class PromiseImpl {
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
// ...其他程式碼
}
}
2.2.7.4 如果 onRejected
不是函式且 promise1
已經 rejected
,則 promise2
必須 rejected
且返回與 promise1
相同的原因。
解釋:同理,原因也要透傳:
Promise.reject('reason').catch().catch(reason => console.log(reason)) // 'reason'
class PromiseImpl {
then(onFulfilled, onRejected) {
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
// ...其他程式碼
}
}
2.3 Promise 解決過程
Promise
解決過程是一個抽象的操作,輸入一個 promise
和一個值,這裡我們將其命名為 promiseResolutionProcedure(promise, x, resolve, reject)
,並在呼叫時傳入 resolve
和 reject
兩個方法, 分別用於在 fulfilled
和 rejected
時呼叫。
2.3.1 如果 promise
和 x
為同一個物件
如果 promise
和 x
為同一個物件,拒絕該 promise
,原因為 TypeError
。
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// 2.3.1 If `promise` and `x` refer to the same object,
// reject `promise` with a `TypeError` as the reason
if (promise === x) {
return reject(new TypeError('`promise` and `x` refer to the same object, see: https://promisesaplus.com/#point-48'))
}
// ...其他程式碼
}
2.3.2 如果 x
是一個 Promise 物件
如果 x
是一個 Promise
物件,則需要遞迴執行:
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他程式碼
// 2.3.2 If `x` is a promise, adopt its state:
// 2.3.2.1 If `x` is pending, `promise` must remain pending until `x` is fulfilled or rejected.
// 2.3.2.2 If/when `x` is fulfilled, fulfill `promise` with the same value.
// 2.3.2.3 If/when `x` is rejected, reject `promise` with the same reason.
if (x instanceof PromiseImpl) {
return x.then(
value => promiseResolutionProcedure(promise, value, resolve, reject),
reason => reject(reason)
)
}
}
2.3.3 如果 x
是一個物件或函式
如果 x
是一個物件或函式:
2.3.3.1 將 x.then
賦值給 x
解釋:這樣做有兩個目的:
- 避免對
x.then
的多次訪問(這也是日常開發中的一個小技巧,當要多次訪問一個物件的同一屬性時,通常我們會使用一個變數將該屬性儲存起來,避免多次進行原型鏈查詢) - 執行過程中
x.then
的值可能被改變
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他程式碼
// 2.3.3 Otherwise, if x is an object or function
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
// 2.3.3.1 Let `then` be `x.then`
let then = x.then
}
}
2.3.3.2 如果在對 x.then
取值時丟擲異常 e
,則拒絕該 promise
,原因為 e
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他程式碼
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
// 2.3.3.1 Let `then` be `x.then`
} catch (e) {
// 2.3.3.2 If retrieving the property `x.then` results in a thrown exception `e`,
// reject `promise` with `e` as the reason.
reject(e)
}
}
}
2.3.3.3 如果 then
是函式
如果 then
是函式,則將 x
作為 then
的作用域,並呼叫 then
,同時傳入兩個回撥函式,第一個名為 resolvePromise
,第二個名為 rejectPromise
。
解釋:意思就是在呼叫 then
的時候要指定其 this
值為 x
,同時需要傳入兩個回撥函式。這時候用 call()
來實現是最好不過了:
then.call(x, resolvePromise, rejectPromise)
2.3.3.3.1 ~ 2.3.3.3.4 總結起來的意思如下:
2.3.3.3.1 如果
resolvePromise
被呼叫,則遞迴呼叫promiseResolutionProcedure
,值為y
。因為Promise
中可以巢狀Promise
:then.call( x, y => promiseResolutionProcedure(promise, y, resolve, reject), rejectPromise )
2.3.3.3.2 如果
rejectPromise
被呼叫,引數為r
,則拒絕執行Promise
,原因為r
:then.call( x, y => promiseResolutionProcedure(promise, y, resolve, reject), // resolvePromise r => reject(r) // rejectPromise )
2.3.3.3.3 如果
resolvePromise
和rejectPromise
均被呼叫,或者被同一引數呼叫了多次,則只執行首次呼叫解釋:這裡我們可以通過設定一個標誌位來解決,然後分別在
resolvePromise
和rejectPromise
這兩個回撥函式內做判斷:// 初始化時設定為 false let called = false if (called) { return } // `resolvePromise` 和 `rejectPromise` 被呼叫時設定為 true called = true
- 2.3.3.3.4 呼叫
then
丟擲異常e
時,如果這時resolvePromise
或rejectPromise
已經被呼叫,則忽略;否則拒絕該Promise
,原因為e
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他程式碼
let called = false
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
let then = x.then
if (typeof then === 'function') {
// 2.3.3.3 If `then` is a function, call it with `x` as `this`,
// first argument `resolvePromise`, and second argument `rejectPromise`
then.call(
// call it with `x` as `this`
x,
// `resolvePromise`
// 2.3.3.3.1 If/when `resolvePromise` is called with a value `y`,
// run `[[Resolve]](promise, y)`.
y => {
// 2.3.3.3.3 If both `resolvePromise` and `rejectPromise` are called,
// or multiple calls to the same argument are made,
// the first call takes precedence, and any further calls are ignored.
if (called) {
return
}
called = true
promiseResolutionProcedure(promise, y, resolve, reject)
},
// `rejectPromise`
// 2.3.3.3.2 If/when `rejectPromise` is called with a reason `r`,
// reject `promise` with `r`
r => {
// 2.3.3.3.3
if (called) {
return
}
called = true
reject(r)
}
)
} else {
// 2.3.3.4 If `then` is not a function, fulfill `promise` with `x`
resolve(x)
}
} catch (e) {
// 2.3.3.3.3
if (called) {
return
}
called = true
// 2.3.3.2 If retrieving the property `x.then` results in a thrown exception `e`,
// reject `promise` with `e` as the reason.
// 2.3.3.3.4 If calling `then` throws an exception `e`
// 2.3.3.3.4.1 If `resolvePromise` or `rejectPromise` have been called, ignore it
// 2.3.3.3.4.2 Otherwise, reject `promise` with `e` as the reason
reject(e)
}
}
}
2.3.3.4 如果 then
不是函式,則執行該 promise
,引數為 x
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他程式碼
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
let then = x.then
if (typeof then === 'function') {
// 2.3.3.3
} else {
// 2.3.3.4 If `then` is not a function, fulfill `promise` with `x`
resolve(x)
}
} catch (e) {
}
}
}
2.3.4 如果 x
既不是物件也不是函式
如果 x
既不是物件也不是函式,執行該 promise
,引數為 x
:
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他程式碼
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
// 2.3.3
} else {
// 2.3.4 If `x` is not an object or function, fulfill `promise` with `x`
resolve(x)
}
}
至此,我們的自定義 Promise
已經完成。這是原始碼:promise-aplus-implementing。
4. 如何測試
Promise/A+ 規範提供了一個測試指令碼:promises-tests,你可以用它來測試你的實現是否符合規範。
在 PromiseImpl.js
裡新增以下程式碼:
// PromiseImpl.js
const STATUS_PENDING = 'pending'
const STATUS_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'
const invokeArrayFns = (fns, arg) => {
// ...相關程式碼
}
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...相關程式碼
}
class PromiseImpl {
// ...相關程式碼
}
PromiseImpl.defer = PromiseImpl.deferred = () => {
const dfd = {}
dfd.promise = new PromiseImpl((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = PromiseImpl
然後在 package.json
裡新增 scripts
:
// package.json
{
"scripts": {
"test": "promises-aplus-tests PromiseImpl.js"
}
}
然後,執行 npm run test
,執行測試指令碼,如果你的實現符合 Promise/A+ 規範的話,所有測試用例均會通過。
5. 其他 API 的實現
Promise/A+ 規範只規定了 then()
方法的實現,其他的如 catch()
、finally()
等方法並不在規範裡。但就實現而言,這些方法可以通過對 then()
方法或其他方式進行二次封裝來實現。
另外,像是 all()
、race()
等這些方法,其引數為一個可迭代物件,如 Array
、Set
、Map
等。那麼,什麼是可迭代物件根據 ES6 中的規範,要成為可迭代物件,一個物件必須實現 @@iterator
方法,即該物件必須有一個名為 @@iterator
的屬性,通常我們使用常量 Symbol.iterator 來訪問該屬性。
根據以上資訊,判斷一個引數是否為可迭代物件,其實現如下:
const isIterable = value => !!value && typeof value[Symbol.iterator] === 'function'
Promise.resolve
Promise.resolve(value)
,靜態方法,其引數有以下幾種可能:
- 引數是
Promise
物件 - 引數是
thenable
物件(擁有then()
方法的物件) - 引數是原始值或不具有
then()
方法的物件 - 引數為空
因此,其返回值由其引數決定:有可能是一個具體的值,也有可能是一個 Promise
物件:
class PromiseImpl {
static resolve(value) {
return new PromiseImpl((resolve, reject) => resolve(value))
}
}
Promise.reject
Promise.reject(reason)
,靜態方法,引數為 Promise
拒絕執行時的原因,同時返回一個 Promise
物件,狀態為 rejected
:
class PromiseImpl {
static reject(reason) {
return new PromiseImpl((resolve, reject) => reject(reason))
}
}
Promise.prototype.catch
Promise.prototype.catch(onRejected)
其實就是 then(null, onRejected)
的語法糖:
class PromiseImpl {
catch(onRejected) {
return this.then(null, onRejected)
}
}
Promise.prototype.finally
顧名思義,不管 Promise
最後的結果是 fulfilled
還是 rejected
,finally
裡的語句都會執行:
class PromiseImpl {
finally(onFinally) {
return this.then(
value => PromiseImpl.resolve(onFinally()).then(() => value),
reason => PromiseImpl.resolve(onFinally()).then(() => { throw reason })
)
}
}
Promise.all
Promise.all(iterable)
,靜態方法,引數為可迭代物件:
- 只有當
iterable
裡所有的Promise
都成功執行後才會fulfilled
,回撥函式的返回值為所有Promise
的返回值組成的陣列,順序與iterable
的順序保持一致。 - 一旦有一個
Promise
拒絕執行,則狀態為rejected
,並且將第一個拒絕執行的Promise
的原因作為回撥函式的返回值。 - 該方法會返回一個
Promise
。
class PromiseImpl {
static all(iterable) {
if (!isIterable(iterable)) {
return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
}
return new PromiseImpl((resolve, reject) => {
// `fulfilled` 的 Promise 數量
let fulfilledCount = 0
// 收集 Promise `fulfilled` 時的值
const res = []
for (let i = 0; i < iterable.length; i++) {
const iterator = iterable[i]
iterator.then(
value => {
res[i] = value
fulfilledCount++
if (fulfilledCount === iterable.length) {
resolve(res)
}
},
reason => reject(reason)
)
}
})
}
}
測試一下:
const promise1 = Promise.resolve(42)
const promise2 = new PromiseImpl((resolve, reject) => setTimeout(() => resolve('value2'), 1000))
PromiseImpl.all([
promise1,
promise2
]).then(values => console.log('values:', values))
結果:
values: [42, 'value2']
好像挺完美的,但是事實果真如此嗎?仔細看看我們的程式碼,假如 iterable
是一個空的陣列呢?假如 iterable
裡有不是 Promise
的呢?就像這樣:
PromiseImpl.all([])
PromiseImpl.all([promise1, promise2, 'value3'])
這種情況下執行前面的程式碼,是得不到任何結果的。因此,程式碼還要再改進一下:
class PromiseImpl {
static all(iterable) {
if (!isIterable(iterable)) {
return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
}
return new PromiseImpl((resolve, reject) => {
// `fulfilled` 的 Promise 數量
let fulfilledCount = 0
// 收集 Promise `fulfilled` 時的值
const res = []
// - 填充 `res` 的值
// - 增加 `fulfilledCount`
// - 判斷所有 `Promise` 是否已經全部成功執行
const processRes = (index, value) => {
res[index] = value
fulfilledCount++
if (fulfilledCount === iterable.length) {
resolve(res)
}
}
if (iterable.length === 0) {
resolve(res)
} else {
for (let i = 0; i < iterable.length; i++) {
const iterator = iterable[i]
if (iterator && typeof iterator.then === 'function') {
iterator.then(
value => processRes(i, value),
reason => reject(reason)
)
} else {
processRes(i, iterator)
}
}
}
})
}
}
現在再來測試一下:
const promise1 = PromiseImpl.resolve(42)
const promise2 = 3
const promise3 = new PromiseImpl((resolve, reject) => setTimeout(() => resolve('value3'), 1000))
PromiseImpl.all([
promise1,
promise2,
promise3,
'a'
]).then(values => console.log('values:', values))
// 結果:values: [42, 3, 'value3', 'a']
PromiseImpl.all([]).then(values => console.log('values:', values))
// 結果:values: []
Promise.allSettled
Promise.allSettled(iterable)
,靜態方法,引數為可迭代物件:
- 當
iterable
裡所有的Promise
都成功執行或拒絕執行後才完成,返回值是一個物件陣列。 - 如果有巢狀
Promise
,需要等待該Promise
完成。 - 返回一個新的
Promise
物件。
對於 allSettled
,我們可以在 all
的基礎上進行封裝:
class PromiseImpl {
static allSettled(iterable) {
if (!isIterable(iterable)) {
return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
}
const promises = iterable.map(iterator => PromiseImpl.resolve(iterator).then(
value => ({ status: STATUS_FULFILLED, value }),
reason => ({ status: STATUS_REJECTED, reason })
))
return PromiseImpl.all(promises)
}
}
測試結果:
PromiseImpl.allSettled([
PromiseImpl.resolve(42),
PromiseImpl.reject('Oops!'),
PromiseImpl.resolve(PromiseImpl.resolve(4242)),
'a'
]).then(values => console.log(values))
// 結果:
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: 'Oops!' },
// { status: 'fulfilled', value: 4242 },
// { status: 'fulfilled', value: 'a' }
// ]
Promise.race
Promise.race(iterable)
,靜態方法,引數為可迭代物件:
- 當
iterable
裡的任一Promise
成功執行或拒絕執行時,使用該Promise
成功返回的值或拒絕執行的原因 - 如果
iterable
為空,則處於pending
狀態 - 返回一個新的
Promise
物件
例如:
Promise.race([
Promise.resolve(42),
Promise.reject('Oops!'),
'a'
]).then(values => console.log(values))
.catch(reason => console.log(reason))
// 結果:42
Promise.race([
Promise.reject('Oops!'),
Promise.resolve(42),
'a'
]).then(values => console.log(values))
.catch(reason => console.log(reason))
// 結果:Oops!
實現如下:
class PromiseImpl {
static race(iterable) {
if (!isIterable(iterable)) {
return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
}
return new PromiseImpl((resolve, reject) => {
if (iterable.length === 0) {
return
} else {
for (let i = 0; i < iterable.length; i++) {
const iterator = iterable[i]
if (iterator && typeof iterator.then === 'function') {
iterator.then(
value => resolve(value),
reason => reject(reason)
)
return
} else {
resolve(iterator)
return
}
}
}
})
}
}
這裡要注意一點:別忘了使用 return
來結束 for
迴圈。
測試結果:
PromiseImpl.race([
PromiseImpl.resolve(42),
PromiseImpl.reject('Oops!'),
'a'
]).then(values => console.log(values))
.catch(reason => console.log(reason))
// 結果:42
PromiseImpl.race([
PromiseImpl.reject('Oops!'),
PromiseImpl.resolve(42),
'a'
]).then(values => console.log(values))
.catch(reason => console.log(reason))
// 結果:'Oops!'
PromiseImpl.race([
'a',
PromiseImpl.reject('Oops!'),
PromiseImpl.resolve(42)
]).then(values => console.log(values))
.catch(reason => console.log(reason))
// 結果:'a'
6. 共同探討
- 在 2.3.3.1 把
x.then
賦值給then
中,什麼情況下x.then
的指向會被改變? - 在 2.3.3.3 如果
then
是函式 中,除了使用call()
之外,還有什麼其他方式實現嗎?
7. 總結
實現 Promise
,基本分為三個步驟:
- 定義
Promise
的狀態 - 實現
then
方法 - 實現
Promise
解決過程
8. 寫在最後
以前,我在意能不能自己實現一個 Promise
,到處找文章,這塊程式碼 Ctrl+C
,那塊程式碼 Ctrl+V
。現在,我看重的是實現的過程,在這個過程中,你不僅會對 Promise
更加熟悉,還可以學習如何將規範一步步轉為實際程式碼。做對的事,遠比把事情做對重要。
如果你覺得這篇文章對你有幫助,還請:點贊、收藏、轉發;如果你有疑問,請在評論區寫出來,我們一起探討。同時也歡迎關注我的公眾號:前端筆記。