promise 是 ES6 中新增的一種異步解決方案,在日常開發(fā)中也經(jīng)常能看見它的身影汽煮,例如原生的 fetch API 就是基于 promise 實現(xiàn)的棚唆。那么 promise 有哪些特性,如何實現(xiàn)一個具有 promise/A+ 規(guī)范的 promise 呢翎卓?
promise 特性
首先我們整理一下 promise 的一些基本特性和 API摆寄,完整的 promise/A+ 規(guī)范可以參考 【翻譯】Promises/A+規(guī)范
- 狀態(tài)機(jī)
- 具有 pending微饥、fulfilled、rejected 三個狀態(tài)
- 只能由 pending -> fulfilled 和 pending -> rejected 這兩種狀態(tài)變化矩肩,且一經(jīng)改變之后狀態(tài)不可再變
- 成功時必須有一個不可改變的值 value肃续,失敗時必須有一個不可改變的拒因 reason
- 構(gòu)造函數(shù)
- Promise 接受一個函數(shù)作為參數(shù),函數(shù)擁有兩個參數(shù) fulfill 和 reject
- fulfill 將 promise 狀態(tài)從 pending 置為 fulfilled刽酱,返回操作的結(jié)果
- reject 將 promise 狀態(tài)從 pending 置為 rejected棵里,返回產(chǎn)生的錯誤
- then 方法
- 接受兩個參數(shù) onFulfilled 和 onRejected,分別表示 promise 成功和失敗的回調(diào)
- 返回值會作為參數(shù)傳遞到下一個 then 方法的參數(shù)中
- 異步處理
- 鏈?zhǔn)秸{(diào)用
- 其他 API
- catch典蝌、finally
- resolve头谜、reject乔夯、race、all 等
實現(xiàn)
接下來我們逐步實現(xiàn)一個具有 promise/A+ 規(guī)范的 promise
基本實現(xiàn)
先定義一個常量侧纯,表示 promise 的三個狀態(tài)
const STATE = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
}
然后在 promise 中初始化兩個參數(shù) value 和 reason貌嫡,分別表示狀態(tài)為 fulfill 和 reject 時的值,接著定義兩個函數(shù)馋辈,函數(shù)內(nèi)部更新狀態(tài)以及相應(yīng)的字段值墩新,分別在成功和失敗的時候執(zhí)行,然后將這兩個函數(shù)傳入構(gòu)造函數(shù)的函數(shù)參數(shù)中绵疲,如下:
class MyPromise {
constructor(fn) {
// 初始化
this.state = STATE.PENDING
this.value = null
this.reason = null
// 成功
const fulfill = (value) => {
// 只有 state 為 pending 時盔憨,才可以更改狀態(tài)
if (this.state === STATE.PENDING) {
this.state = STATE.FULFILLED
this.value = value
}
}
// 失敗
const reject = (reason) => {
if (this.state === STATE.PENDING) {
this.state = STATE.REJECTED
this.reason = reason
}
}
// 執(zhí)行函數(shù)出錯時調(diào)用 reject
try {
fn(fulfill, reject)
} catch (e) {
reject(e)
}
}
}
接下來初步實現(xiàn)一個 then 方法郁岩,當(dāng)當(dāng)前狀態(tài)是 fulfulled 時,執(zhí)行成功回調(diào)萍摊,當(dāng)前狀態(tài)為 rejected 時蝴乔,執(zhí)行失敗回調(diào):
class MyPromise {
constructor(fn) {
//...
}
then(onFulfilled, onRejected) {
if (this.state === STATE.FULFILLED) {
onFulfilled(this.value)
}
if (this.state === STATE.REJECTED) {
onRejected(this.reason)
}
}
}
這個時候一個簡單的 MyPromise 就實現(xiàn)了薇正,但是此時它還只能處理同步任務(wù)囚衔,對于異步操作卻無能為力
異步處理
要想處理異步操作练湿,可以利用隊列的特性,將回調(diào)函數(shù)先緩存起來辽俗,等到異步操作的結(jié)果返回之后篡诽,再去執(zhí)行相應(yīng)的回調(diào)函數(shù)杈女。
具體實現(xiàn)來看,在 then 方法中增加判斷翰蠢,若為 pending 狀態(tài)啰劲,將傳入的函數(shù)寫入對應(yīng)的回調(diào)函數(shù)隊列蝇裤;在初始化 promise 時利用兩個數(shù)組分別保存成功和失敗的回調(diào)函數(shù)隊列,并在 fulfill 和 reject 回調(diào)中增加它們酥泞。如下:
class MyPromise {
constructor(fn) {
// 初始化
this.state = STATE.PENDING
this.value = null
this.reason = null
// 保存數(shù)組
this.fulfilledCallbacks = []
this.rejectedCallbacks = []
// 成功
const fulfill = (value) => {
// 只有 state 為 pending 時芝囤,才可以更改狀態(tài)
if (this.state === STATE.PENDING) {
this.state = STATE.FULFILLED
this.value = value
this.fulfilledCallbacks.forEach(cb => cb())
}
}
// 失敗
const reject = (reason) => {
if (this.state === STATE.PENDING) {
this.state = STATE.REJECTED
this.reason = reason
this.rejectedCallbacks.forEach(cb => cb())
}
}
// 執(zhí)行函數(shù)出錯時調(diào)用 reject
try {
fn(fulfill, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
if (this.state === STATE.FULFILLED) {
onFulfilled(this.value)
}
if (this.state === STATE.REJECTED) {
onRejected(this.reason)
}
// 當(dāng) then 是 pending 時,將這兩個狀態(tài)寫入數(shù)組中
if (this.state === STATE.PENDING) {
this.fulfilledCallbacks.push(() => {
onFulfilled(this.value)
})
this.rejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
鏈?zhǔn)秸{(diào)用
接下來對 MyPromise 進(jìn)行進(jìn)一步改造羡藐,使其能夠支持鏈?zhǔn)秸{(diào)用仆嗦,使用過 jquery 等庫應(yīng)該對于鏈?zhǔn)秸{(diào)用非常熟悉先壕,它的原理就是調(diào)用者返回它本身垃僚,在這里的話就是要讓 then 方法返回一個 promise 即可,還有一點就是對于返回值的傳遞:
class MyPromise {
constructor(fn) {
//...
}
then(onFulfilled, onRejected) {
return new MyPromise((fulfill, reject) => {
if (this.state === STATE.FULFILLED) {
// 將返回值傳入下一個 fulfill 中
fulfill(onFulfilled(this.value))
}
if (this.state === STATE.REJECTED) {
// 將返回值傳入下一個 reject 中
reject(onRejected(this.reason))
}
// 當(dāng) then 是 pending 時栽燕,將這兩個狀態(tài)寫入數(shù)組中
if (this.state === STATE.PENDING) {
this.fulfilledCallbacks.push(() => {
fulfill(onFulfilled(this.value))
})
this.rejectedCallbacks.push(() => {
reject(onRejected(this.reason))
})
}
})
}
}
實現(xiàn)到這一步的 MyPromise 已經(jīng)可以支持異步操作碍岔、鏈?zhǔn)秸{(diào)用朵夏、傳遞返回值侍郭,算是一個簡易版的 promise,一般來說面試時需要手寫一個 promise 時猛计,到這個程度就足夠了爆捞,完整實現(xiàn) promise/A+ 規(guī)范在面試這樣一個較短的時間內(nèi)也不太現(xiàn)實煮甥。
到這一步的完整代碼可以參考 promise3.js
promise/A+ 規(guī)范
promise/A+ 規(guī)范中規(guī)定,onFulfilled/onRejected 返回一個值 x卖局,對 x 需要作以下處理:
- 如果 x 與 then 方法返回的 promise 相等砚偶,拋出一個
TypeError
錯誤 - 如果 x 是一個
Promise
,則保持 then 方法返回的 promise 的值與 x 的值一致 - 如果 x 是對象或函數(shù)均芽,則將
x.then
賦值給then
并調(diào)用- 如果
then
是一個函數(shù)单鹿,則將 x 作為作用域this
調(diào)用仲锄,并傳遞兩個參數(shù)resolvePromise
和rejectPromise
,如果resolvePromise
和rejectPromise
均被調(diào)用或者被調(diào)用多次是趴,則采用首次調(diào)用并忽略剩余調(diào)用 - 如果調(diào)用
then
方法出錯,則以拋出的錯誤 e 為拒因拒絕 promise - 如果
then
不是函數(shù)富雅,則以 x 為參數(shù)執(zhí)行 promise
- 如果
- 如果 x 是其他值没佑,則以 x 為參數(shù)執(zhí)行 promise
接下來對上一步實現(xiàn)的 MyPromise 進(jìn)行進(jìn)一步優(yōu)化,使其符合 promise/A+ 規(guī)范:
class MyPromise {
constructor(fn) {
//...
}
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((fulfill, reject) => {
if (this.state === STATE.FULFILLED) {
try {
const x = onFulfilled(this.value)
generatePromise(promise2, x, fulfill, reject)
} catch (e) {
reject(e)
}
}
if (this.state === STATE.REJECTED) {
try {
const x = onRejected(this.reason)
generatePromise(promise2, x, fulfill, reject)
} catch (e) {
reject(e)
}
}
// 當(dāng) then 是 pending 時,將這兩個狀態(tài)寫入數(shù)組中
if (this.state === STATE.PENDING) {
this.fulfilledCallbacks.push(() => {
try {
const x = onFulfilled(this.value)
generatePromise(promise2, x, fulfill, reject)
} catch(e) {
reject(e)
}
})
this.rejectedCallbacks.push(() => {
try {
const x = onRejected(this.reason)
generatePromise(promise2, x, fulfill, reject)
} catch (e) {
reject(e)
}
})
}
})
return promise2
}
}
這里將處理返回值 x 的行為封裝成為了一個函數(shù) generatePromise
待秃,實現(xiàn)如下:
const generatePromise = (promise2, x, fulfill, reject) => {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 如果 x 是 promise痹屹,調(diào)用它的 then 方法繼續(xù)遍歷
if (x instanceof MyPromise) {
x.then((value) => {
generatePromise(promise2, value, fulfill, reject)
}, (e) => {
reject(e)
})
} else if (x != null && (typeof x === 'object' || typeof x === 'function')) {
// 防止重復(fù)調(diào)用志衍,成功和失敗只能調(diào)用一次
let called;
// 如果 x 是對象或函數(shù)
try {
const then = x.then
if (typeof then === 'function') {
then.call(x, (y) => {
if (called) return;
called = true;
// 說明 y是 promise楼肪,繼續(xù)遍歷
generatePromise(promise2, y, fulfill, reject)
}, (r) => {
if (called) return;
called = true;
reject(r)
})
} else {
fulfill(x)
}
} catch(e) {
if (called) return
called = true
reject(e)
}
} else {
fulfill(x)
}
}
promise/A+ 規(guī)范中還規(guī)定,對于 promise2 = promise1.then(onFulfilled, onRejected)
- onFulfilled/onRejected 必須異步調(diào)用肩钠,不能同步
- 如果 onFulfilled 不是函數(shù)且 promise1 成功執(zhí)行蔬将, promise2 必須成功執(zhí)行并返回相同的值
- 如果 onRejected 不是函數(shù)且 promise1 拒絕執(zhí)行, promise2 必須拒絕執(zhí)行并返回相同的拒因
對于 then 方法做最后的完善惫东,增加 setTimeout 模擬異步調(diào)用毙石,增加對于 onFulfilled 和 onRejected 方法的判斷:
class MyPromise {
constructor(fn) {
//...
}
then(onFulfilled, onRejected) {
// 處理 onFulfilled 和 onRejected
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }
const promise2 = new MyPromise((fulfill, reject) => {
// setTimeout 宏任務(wù)徐矩,確保onFulfilled 和 onRejected 異步執(zhí)行
if (this.state === STATE.FULFILLED) {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
generatePromise(promise2, x, fulfill, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.state === STATE.REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason)
generatePromise(promise2, x, fulfill, reject)
} catch (e) {
reject(e)
}
}, 0)
}
// 當(dāng) then 是 pending 時滤灯,將這兩個狀態(tài)寫入數(shù)組中
if (this.state === STATE.PENDING) {
this.fulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
generatePromise(promise2, x, fulfill, reject)
} catch(e) {
reject(e)
}
}, 0)
})
this.rejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
generatePromise(promise2, x, fulfill, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
return promise2
}
}
實現(xiàn) promise/A+ 規(guī)范的 promise 完整代碼可以參考 promise4.js
如何知道你實現(xiàn)的 promise 是否遵循 promise/A+ 規(guī)范呢鳞骤?可以利用 promises-aplus-tests 這樣一個 npm 包來進(jìn)行相應(yīng)測試
其他 API
這里對其他常用的 promise API 進(jìn)行了實現(xiàn)
catch、finally
class MyPromise {
constructor(fn) {
//...
}
then(onFulfilled, onRejected) {
//...
}
catch(onRejected) {
return this.then(null, onRejected)
}
finally(callback) {
return this.then(callback, callback)
}
}
Promise.resolve
返回一個 resolved 狀態(tài)的 Promise 對象
MyPromise.resolve = (value) => {
// 傳入 promise 類型直接返回
if (value instanceof MyPromise) return value
// 傳入 thenable 對象時,立即執(zhí)行 then 方法
if (value !== null && typeof value === 'object') {
const then = value.then
if (then && typeof then === 'function') return new MyPromise(value.then)
}
return new MyPromise((resolve) => {
resolve(value)
})
}
Promise.reject
返回一個 rejected 狀態(tài)的 Promise 對象
MyPromise.reject = (reason) => {
// 傳入 promise 類型直接返回
if (reason instanceof MyPromise) return reason
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
Promise.race
返回一個 promise渤滞,一旦迭代器中的某個 promise 狀態(tài)改變榴嗅,返回的 promise 狀態(tài)隨之改變
MyPromise.race = (promises) => {
return new MyPromise((resolve, reject) => {
// promises 可以不是數(shù)組录肯,但必須存在 Iterator 接口,因此采用 for...of 遍歷
for(let promise of promises) {
// 如果當(dāng)前值不是 Promise优炬,通過 resolve 方法轉(zhuǎn)為 promise
if (promise instanceof MyPromise) {
promise.then(resolve, reject)
} else {
MyPromise.resolve(promise).then(resolve, reject)
}
}
})
}
Promise.all
返回一個 promise蠢护,只有迭代器中的所有的 promise 均變?yōu)?fulfilled养涮,返回的 promise 才變?yōu)?fulfilled眉抬,迭代器中出現(xiàn)一個 rejected蜀变,返回的 promise 變?yōu)?rejected
MyPromise.all = (promises) => {
return new MyPromise((resolve, reject) => {
const arr = []
// 已返回數(shù)
let count = 0
// 當(dāng)前索引
let index = 0
// promises 可以不是數(shù)組介评,但必須存在 Iterator 接口们陆,因此采用 for...of 遍歷
for(let promise of promises) {
// 如果當(dāng)前值不是 Promise,通過 resolve 方法轉(zhuǎn)為 promise
if (!(promise instanceof MyPromise)) {
promise = MyPromise.resolve(promise)
}
// 使用閉包保證異步返回數(shù)組順序
((i) => {
promise.then((value) => {
arr[i] = value
count += 1
if (count === promises.length || count === promises.size) {
resolve(arr)
}
}, reject)
})(index)
// index 遞增
index += 1
}
})
}
Promise.allSettled
只有等到迭代器中所有的 promise 都返回杂腰,才會返回一個 fulfilled 狀態(tài)的 promise喂很,并且返回的 promise 狀態(tài)總是 fulfilled雾袱,不會返回 rejected 狀態(tài)
MyPromise.allSettled = (promises) => {
return new MyPromise((resolve, reject) => {
const arr = []
// 已返回數(shù)
let count = 0
// 當(dāng)前索引
let index = 0
// promises 可以不是數(shù)組芹橡,但必須存在 Iterator 接口望伦,因此采用 for...of 遍歷
for(let promise of promises) {
// 如果當(dāng)前值不是 Promise屯伞,通過 resolve 方法轉(zhuǎn)為 promise
if (!(promise instanceof MyPromise)) {
promise = MyPromise.resolve(promise)
}
// 使用閉包保證異步返回數(shù)組順序
((i) => {
promise.then((value) => {
arr[i] = value
count += 1
if (count === promises.length || count === promises.size) {
resolve(arr)
}
}, (err) => {
arr[i] = err
count += 1
if (count === promises.length || count === promises.size) {
resolve(arr)
}
})
})(index)
// index 遞增
index += 1
}
})
}
本文如有錯誤,歡迎批評指正~
參考