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 } }) } 複製代碼
本文若有錯誤,歡迎批評指正~
參考