promise 是 ES6 中新增的一種異步解決方案,在平常開發中也常常能看見它的身影,例如原生的 fetch API 就是基於 promise 實現的。那麼 promise 有哪些特性,如何實現一個具備 promise/A+ 規範的 promise 呢?javascript
首先咱們整理一下 promise 的一些基本特性和 API,完整的 promise/A+ 規範能夠參考 【翻譯】Promises/A+規範java
接下來咱們逐步實現一個具備 promise/A+ 規範的 promisejquery
先定義一個常量,表示 promise 的三個狀態git
const STATE = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
}
複製代碼
而後在 promise 中初始化兩個參數 value 和 reason,分別表示狀態爲 fulfill 和 reject 時的值,接着定義兩個函數,函數內部更新狀態以及相應的字段值,分別在成功和失敗的時候執行,而後將這兩個函數傳入構造函數的函數參數中,以下:es6
class MyPromise {
constructor(fn) {
// 初始化
this.state = STATE.PENDING
this.value = null
this.reason = null
// 成功
const fulfill = (value) => {
// 只有 state 爲 pending 時,才能夠更改狀態
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
}
}
// 執行函數出錯時調用 reject
try {
fn(fulfill, reject)
} catch (e) {
reject(e)
}
}
}
複製代碼
接下來初步實現一個 then 方法,噹噹前狀態是 fulfulled 時,執行成功回調,當前狀態爲 rejected 時,執行失敗回調:github
class MyPromise {
constructor(fn) {
//...
}
then(onFulfilled, onRejected) {
if (this.state === STATE.FULFILLED) {
onFulfilled(this.value)
}
if (this.state === STATE.REJECTED) {
onRejected(this.reason)
}
}
}
複製代碼
這個時候一個簡單的 MyPromise 就實現了,可是此時它還只能處理同步任務,對於異步操做卻無能爲力面試
要想處理異步操做,能夠利用隊列的特性,將回調函數先緩存起來,等到異步操做的結果返回以後,再去執行相應的回調函數。npm
具體實現來看,在 then 方法中增長判斷,若爲 pending 狀態,將傳入的函數寫入對應的回調函數隊列;在初始化 promise 時利用兩個數組分別保存成功和失敗的回調函數隊列,並在 fulfill 和 reject 回調中增長它們。以下:數組
class MyPromise {
constructor(fn) {
// 初始化
this.state = STATE.PENDING
this.value = null
this.reason = null
// 保存數組
this.fulfilledCallbacks = []
this.rejectedCallbacks = []
// 成功
const fulfill = (value) => {
// 只有 state 爲 pending 時,才能夠更改狀態
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())
}
}
// 執行函數出錯時調用 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)
}
// 當 then 是 pending 時,將這兩個狀態寫入數組中
if (this.state === STATE.PENDING) {
this.fulfilledCallbacks.push(() => {
onFulfilled(this.value)
})
this.rejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
複製代碼
接下來對 MyPromise 進行進一步改造,使其可以支持鏈式調用,使用過 jquery 等庫應該對於鏈式調用很是熟悉,它的原理就是調用者返回它自己,在這裏的話就是要讓 then 方法返回一個 promise 便可,還有一點就是對於返回值的傳遞: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))
}
// 當 then 是 pending 時,將這兩個狀態寫入數組中
if (this.state === STATE.PENDING) {
this.fulfilledCallbacks.push(() => {
fulfill(onFulfilled(this.value))
})
this.rejectedCallbacks.push(() => {
reject(onRejected(this.reason))
})
}
})
}
}
複製代碼
實現到這一步的 MyPromise 已經能夠支持異步操做、鏈式調用、傳遞返回值,算是一個簡易版的 promise,通常來講面試時須要手寫一個 promise 時,到這個程度就足夠了,完整實現 promise/A+ 規範在面試這樣一個較短的時間內也不太現實。
到這一步的完整代碼能夠參考 promise3.js
promise/A+ 規範中規定,onFulfilled/onRejected 返回一個值 x,對 x 須要做如下處理:
TypeError
錯誤Promise
,則保持 then 方法返回的 promise 的值與 x 的值一致x.then
賦值給 then
並調用
then
是一個函數,則將 x 做爲做用域 this
調用,並傳遞兩個參數 resolvePromise
和 rejectPromise
,若是 resolvePromise
和 rejectPromise
均被調用或者被調用屢次,則採用首次調用並忽略剩餘調用then
方法出錯,則以拋出的錯誤 e 爲拒因拒絕 promisethen
不是函數,則以 x 爲參數執行 promise接下來對上一步實現的 MyPromise 進行進一步優化,使其符合 promise/A+ 規範:
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)
}
}
// 當 then 是 pending 時,將這兩個狀態寫入數組中
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 的行爲封裝成爲了一個函數 generatePromise
,實現以下:
const generatePromise = (promise2, x, fulfill, reject) => {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 若是 x 是 promise,調用它的 then 方法繼續遍歷
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')) {
// 防止重複調用,成功和失敗只能調用一次
let called;
// 若是 x 是對象或函數
try {
const then = x.then
if (typeof then === 'function') {
then.call(x, (y) => {
if (called) return;
called = true;
// 說明 y是 promise,繼續遍歷
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+ 規範中還規定,對於 promise2 = promise1.then(onFulfilled, onRejected)
對於 then 方法作最後的完善,增長 setTimeout 模擬異步調用,增長對於 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 宏任務,確保onFulfilled 和 onRejected 異步執行
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)
}
// 當 then 是 pending 時,將這兩個狀態寫入數組中
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
}
}
複製代碼
實現 promise/A+ 規範的 promise 完整代碼能夠參考 promise4.js
如何知道你實現的 promise 是否遵循 promise/A+ 規範呢?能夠利用 promises-aplus-tests 這樣一個 npm 包來進行相應測試
這裏對其餘經常使用的 promise API 進行了實現
class MyPromise {
constructor(fn) {
//...
}
then(onFulfilled, onRejected) {
//...
}
catch(onRejected) {
return this.then(null, onRejected)
}
finally(callback) {
return this.then(callback, callback)
}
}
複製代碼
返回一個 resolved 狀態的 Promise 對象
MyPromise.resolve = (value) => {
// 傳入 promise 類型直接返回
if (value instanceof MyPromise) return value
// 傳入 thenable 對象時,當即執行 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)
})
}
複製代碼
返回一個 rejected 狀態的 Promise 對象
MyPromise.reject = (reason) => {
// 傳入 promise 類型直接返回
if (reason instanceof MyPromise) return reason
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
複製代碼
返回一個 promise,一旦迭代器中的某個 promise 狀態改變,返回的 promise 狀態隨之改變
MyPromise.race = (promises) => {
return new MyPromise((resolve, reject) => {
// promises 能夠不是數組,但必須存在 Iterator 接口,所以採用 for...of 遍歷
for(let promise of promises) {
// 若是當前值不是 Promise,經過 resolve 方法轉爲 promise
if (promise instanceof MyPromise) {
promise.then(resolve, reject)
} else {
MyPromise.resolve(promise).then(resolve, reject)
}
}
})
}
複製代碼
返回一個 promise,只有迭代器中的全部的 promise 均變爲 fulfilled,返回的 promise 才變爲 fulfilled,迭代器中出現一個 rejected,返回的 promise 變爲 rejected
MyPromise.all = (promises) => {
return new MyPromise((resolve, reject) => {
const arr = []
// 已返回數
let count = 0
// 當前索引
let index = 0
// promises 能夠不是數組,但必須存在 Iterator 接口,所以採用 for...of 遍歷
for(let promise of promises) {
// 若是當前值不是 Promise,經過 resolve 方法轉爲 promise
if (!(promise instanceof MyPromise)) {
promise = MyPromise.resolve(promise)
}
// 使用閉包保證異步返回數組順序
((i) => {
promise.then((value) => {
arr[i] = value
count += 1
if (count === promises.length || count === promises.size) {
resolve(arr)
}
}, reject)
})(index)
// index 遞增
index += 1
}
})
}
複製代碼
只有等到迭代器中全部的 promise 都返回,纔會返回一個 fulfilled 狀態的 promise,而且返回的 promise 狀態老是 fulfilled,不會返回 rejected 狀態
MyPromise.allSettled = (promises) => {
return new MyPromise((resolve, reject) => {
const arr = []
// 已返回數
let count = 0
// 當前索引
let index = 0
// promises 能夠不是數組,但必須存在 Iterator 接口,所以採用 for...of 遍歷
for(let promise of promises) {
// 若是當前值不是 Promise,經過 resolve 方法轉爲 promise
if (!(promise instanceof MyPromise)) {
promise = MyPromise.resolve(promise)
}
// 使用閉包保證異步返回數組順序
((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
}
})
}
複製代碼
本文若有錯誤,歡迎批評指正~
參考