手寫 Promise 符合 Promise/A+規範
非同步程式設計是前端開發者必需的技能,過去管理非同步的主要機制都是通過函式回撥,然而會出現像“回撥地獄”這樣的問題。為了更好的管理回撥,ES6 增加了一個新的特性 Promise。Promise 是 ES7 中async/await 語法的基礎,是 JavaScript 中處理非同步的標準形式,現實開發中基本離不開 Promise 了。我們接下來會根據 Promises/A+ 規範自己實現一個 Promise。
完整的程式碼可以點選我的 github 進行檢視,如果你喜歡,歡迎 star,如果發現有問題或者錯誤,也歡迎提出來。
Promises/A+ 規範
首先我們來看 Promises/A+ 規範的具體內容,Promises/A+ 規範可以檢視 Promises/A+,下面是翻譯的規範供參考
術語
- promise 是一個有 then 方法的物件或者是函式,行為遵循本規範
- thenable 是一個有then方法的物件或者是函式
- value 是 promise 狀態成功時的值,包括 undefined、thenable、promise
- exception 是一個使用 throw 丟擲的異常值
- reason 是promise狀態失敗時的值
要求
2.1 Promise 狀態
Promise 必須處於以下三個狀態之一: pending, fulfilled 或者 rejected。
2.1.1 如果 promise 在 pending 狀態
2.1.1.1 可以變成 fulfilled 或者是 rejected
2.1.2 如果 promise 在 fulfilled 狀態
2.1.2.1 不會變成其它狀態
2.1.2.2 必須有一個value值
2.1.3 如果 promise 在 rejected 狀態
2.1.3.1 不會變成其它狀態
2.1.3.2 必須有一個 promise 被 reject 的 reason
2.2 then 方法
promise 必須提供一個 then 方法,來訪問最終的結果
promise 的 then 方法接收兩個引數
promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled 和 onRejected 都是可選引數:
2.2.1.1 onFulfilled 必須是函式型別
2.2.1.2 onRejected 必須是函式型別
2.2.2 如果 onFulfilled 是函式:
2.2.2.1 必須在 promise 變成 fulfilled 時,呼叫 onFulfilled,引數是 promise 的 value
2.2.2.2 在 promise 的狀態不是 fulfilled 之前,不能呼叫
2.2.2.3 onFulfilled 只能被呼叫一次
2.2.3 如果 onRejected 是函式:
2.2.3.1 必須在promise變成 rejected 時,呼叫 onRejected,引數是promise的reason
2.2.3.2 在promise的狀態不是 rejected 之前,不能呼叫
2.2.3.3 onRejected 只能被呼叫一次
2.2.4 onFulfilled 和 onRejected 應該是微任務
2.2.5 onFulfilled 和 onRejected 必須作為函式被呼叫
2.2.6 then 方法可能被多次呼叫
2.2.6.1 如果 promise 變成了 fulfilled 態,所有的 onFulfilled 回撥都需要按照 then 的順序執行
2.2.6.2 如果 promise 變成了 rejected 態,所有的 onRejected 回撥都需要按照 then 的順序執行
2.2.7 then 必須返回一個 promise
promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1 onFulfilled 或 onRejected 執行的結果為 x, 執行 resolutionProcedure(promise2, x)
2.2.7.2 如果 onFulfilled 或者 onRejected 執行時丟擲異常e, promise2 需要被 reject
2.2.7.3 如果 onFulfilled 不是一個函式,promise2 以 promise1 的值 fulfilled
2.2.7.4 如果 onRejected 不是一個函式,promise2 以 promise1 的 reason rejected
2.3 The Promise Resolution Procedure
resolutionProcedure(promise2, x, resolve, reject)
2.3.1 如果 promise2 和 x 相等,那麼 promise 執行 reject TypeError
2.3.2 如果 x 是一個 promsie
2.3.2.1 如果 x 是 pending 狀態,那麼 promise 必須要保持 pending 狀態直到 x 變成 fulfilled 或者 rejected 狀態
2.3.2.2 如果 x 是 fulfilled 狀態, fulfill promise with the same value
2.3.2.3 如果 x 是 rejected 狀態, reject promise with the same reason
2.3.3 如果 x 是一個 object 或者 是一個 function
2.3.3.1 let then = x.then.
2.3.3.2 如果 x.then 這步出錯,那麼 reject promise with e as the reason
2.3.3.3 如果 then 是一個函式,then.call(x, resolvePromise, rejectPromise)
2.3.3.3.1 resolvePromiseFn 的 入參是 y, 執行
resolutionProcedure(promise2, y, resolve, reject);
2.3.3.3.2 rejectPromise 的 入參是 r, reject promise with r.
2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都呼叫了,那麼第一個呼叫優先,後面的呼叫忽略。
2.3.3.3.4 如果呼叫then丟擲異常 e
2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已經被呼叫,那麼忽略
2.3.3.3.4.2 否則,reject promise with e as the reason
2.3.3.4 如果 then 不是一個function. fulfill promise with x.
2.3.4 如果 x 不是一個 object 或者 function,fulfill promise with x.
Promise 原始碼實現
接下來我們只要按照規範來實現 Promise 就行了,很顯然規範後半部分看起來是比較複雜的,尤其是 resolutionProcedure 函式的實現,但其實這只是一些實現的細節而已。初看可能不是那麼順暢,那麼強烈建議多看幾遍規範,然後自己多實現幾遍。
1. promise 需要傳遞一個 executor 執行器,執行器立刻執行
2. 執行器 executor 接受兩個引數,分別是 resolve 和 reject
3. promise 只能從 pending 到 rejected, 或者從 pending 到 fulfilled,狀態一旦確認,就不會再改變
4. promise 都有 then 方法,then 接收兩個引數,分別是 promise 成功的回撥 onFulfilled, 和 promise 失敗的回撥 onRejected
6. 如果呼叫 then 時,promise已經成功,則執行 onFulfilled,並將 promise 的值作為引數傳遞進去。 如果 promise 已經失敗,那麼執行 onRejected, 並將 promise 失敗的原因作為引數傳遞進去。如果 promise 的狀態是 pending,需要將 onFulfilled 和 onRejected 函式存放起來,等待狀態確定後,再依次將對應的函式執行。
7. then 的引數 onFulfilled 和 onRejected 可以預設
8. promise 可以 then 多次,promise 的 then 方法返回一個新的 promise
9. 如果 then 返回的是一個結果,那麼就會把這個結果作為引數,傳遞給下一個 then 的成功的回撥 onFulfilled
10. 如果 then 中丟擲了異常,那麼就會把這個異常作為引數,傳遞給下一個 then 的失敗的回撥 onRejected
11. 如果 then 返回的是一個 promise, 那麼需要等這個 promise,那麼會等這個 promise 執行完。promise 如果成功, 就走下一個 then 的成功,如果失敗,就走下一個 then 的失敗
// 定義三種狀態的常量 const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; // pomise 接收一個 executor 執行器,執行器立刻執行 function MyPromise(executor) { const _this = this; // promise 當前的狀態 _this.currentState = PENDING; _this.value = undefined; // 儲存 then 中的回撥,只有當 promise 狀態為 pending 時才會快取,並且每個例項至多快取一個 _this.onFulfilledCallbacks = []; _this.onRejectedCallbacks = []; function resolve(value) { if (value instanceof MyPromise) { // 如果 value 是個 Promise,呼叫 then 方法繼續執行 value.then(resolve, reject); } // 非同步執行,保證執行順序 setTimeout(() => { if (_this.currentState === PENDING) { _this.currentState = FULFILLED; _this.value = value; _this.onFulfilledCallbacks.forEach(fn => fn()); } }); } function reject(reason) { // 非同步執行,保證執行順序 setTimeout(() => { if (_this.currentState === PENDING) { _this.currentState = REJECTED; _this.value = reason; _this.onRejectedCallbacks.forEach(fn => fn()); } }); } try { executor(resolve, reject); } catch(err) { reject(err); } } MyPromise.prototype.constructor = MyPromise; MyPromise.prototype.then = function (onFulfilled, onRejected) { const _this = this; // 2.2.1 onFulfilled 和 onRejected 都是可選引數 // 2.2.5 onFulfilled 和 onRejected 必須作為函式被呼叫 // 2.2.7.3 如果 onFulfilled 不是一個函式,promise2 以promise1的值fulfilled // 2.2.7.4 如果 onRejected 不是一個函式,promise2 以promise1的reason rejected onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : valve => valve; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }; // 2.2.7,then 必須返回一個新的 promise const promise2 = new MyPromise((resolve, reject) => { if (_this.currentState === FULFILLED) { // 2.2.4 保證 onFulfilled,onRjected 非同步執行 setTimeout(() => { try { // 2.2.7.1 onFulfilled 或 onRejected 執行的結果為 x, 呼叫 resolutionProcedure const x = onFulfilled(_this.value); resolutionProcedure(promise2, x, resolve, reject); } catch(err) { // 2.2.7.2 如果 onFulfilled 或者 onRejected 執行時丟擲異常 err, promise2 需要被 reject reject(err); } }); } if (_this.currentState === REJECTED) { setTimeout(() => { try { // 2.2.7.1 onFulfilled 或 onRejected 執行的結果為 x, 呼叫 resolutionProcedure const x = onRejected(_this.value); resolutionProcedure(promise2, x, resolve, reject); } catch(err) { // 2.2.7.2 如果 onFulfilled 或者 onRejected 執行時丟擲異常 err, promise2 需要被 reject reject(err); } }); } if (_this.currentState === PENDING) { _this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { const x = onFulfilled(_this.value); resolutionProcedure(promise2, x, resolve, reject); } catch(err) { reject(err); } }); }); _this.onRejectedCallbacks.push(() => { setTimeout(() => { try { const x = onRejected(_this.value); resolutionProcedure(promise2, x, resolve, reject); } catch(err) { reject(err); } }); }); } }); return promise2; } // 2.3 resolutionProcedure(promise2, x, resolve, reject) function resolutionProcedure(promise2, x, resolve, reject) { // 2.3.1 如果 promise2 和 x 相等,那麼 reject promise with a TypeError if (promise2 === x) { reject(new TypeError('Error')); } // 2.3.2 如果 x 為 Promise,狀態為 pending 需要繼續等待否則執行 if (x instanceof MyPromise) { if (x.currentState === PENDING) { x.then(value => { resolutionProcedure(promise2, value, resolve, reject); }, reject) } else { x.then(resolve, reject); } } // 2.3.3.3.3 reject 或者 resolve 其中一個執行過的話,忽略其他的 let called = false; // 2.3.3,判斷 x 是否為物件或者函式 if ( x && (typeof x === 'object' || typeof x === 'function') ) { try { let then = x.then; // 2.3.3.3 如果 then 是一個函式,then.call(x, resolvePromise, rejectPromise) if (typeof then === 'function') { then.call( x, y => { if (called) return; called = true; // 2.3.3.3.1 resolvePromiseFn 的 入參是 y, 執行 resolutionProcedure(promise2, y, resolve, reject); resolutionProcedure(promise2, y, resolve, reject); }, r => { if (called) return; called = true; // 2.3.3.3.2 rejectPromise 的 入參是 r, reject promise with r. reject(r); } ); } else { // 2.3.3.4 如果 then 不是一個 function. fulfill promise with x. resolve(x); } } catch(err) { // 2.3.3.2 如果 x.then 這步出錯,那麼 reject promise with err as the reason if (called) return; called = true; reject(err) } } else { // 2.3.4 如果 x 不是一個 object 或者 function,fulfill promise with x. resolve(x); } } module.exports = MyPromise;
測試
promises-aplus-tests 這個 npm 包可以幫助我們測試所編寫的 promise 程式碼是否符合 Promises/A+ 的規範。
不過我們需要先增加以下程式碼去提供測試的介面
MyPromise.defer = MyPromise.deferred = function () { let dfd = {}; dfd.promise = new MyPromise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; }
通過 npm 安裝
npm install promises-aplus-tests --save-dev
通過執行 promises-aplus-tests target.js 可以執行測試,promises-aplus-tests 中共有 872 條測試用例。
可以在我的 github 進行檢視,通過 npm run test 來執行測試,可以通過所有的測試用例。
更多精彩內容,歡迎關注微信公眾號~
&n