異步編程是前端開發者必需的技能,過去管理異步的主要機制都是經過函數回調,然而會出現像「回調地獄」這樣的問題。爲了更好的管理回調,ES6
增長了一個新的特性 Promise
。Promise
是 ES7
中 async/await
語法的基礎,是 JavaScript
中處理異步的標準形式,現實開發中基本離不開 Promise
了。咱們接下來會根據 Promises/A+
規範本身實現一個 Promise
。前端
完整的代碼能夠點擊個人 github 進行查看,若是你喜歡,歡迎 star
,若是發現有問題或者錯誤,也歡迎提出來。git
首先咱們來看 Promises/A+
規範的具體內容,Promises/A+
規範能夠查看 Promises/A+
,下面是翻譯的規範供參考github
術語
promise
是一個有 then
方法的對象或者是函數,行爲遵循本規範thenable
是一個有 then
方法的對象或者是函數value
是 promise
狀態成功時的值,包括 undefined
、thenable
、promise
exception
是一個使用 throw
拋出的異常值reason
是 promise
狀態失敗時的值要求
Promise
必須處於如下三個狀態之一: pending
, fulfilled
或者 rejected
。npm
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
promise
必須提供一個 then
方法,來訪問最終的結果編程
promise
的 then
方法接收兩個參數promise
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
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
就好了,很顯然規範後半部分看起來是比較複雜的,尤爲是 resolutionProcedure
函數的實現,但其實這只是一些實現的細節而已。初看可能不是那麼順暢,那麼強烈建議多看幾遍規範,而後本身多實現幾遍。緩存
promise
須要傳遞一個 executor
執行器,執行器馬上執行executor
接受兩個參數,分別是 resolve
和 reject
promise
只能從 pending
到 rejected
, 或者從 pending
到 fulfilled
,狀態一旦確認,就不會再改變promise
都有 then
方法,then
接收兩個參數,分別是 promise
成功的回調 onFulfilled
, 和 promise
失敗的回調 onRejected
then
時,promise
已經成功,則執行 onFulfilled
,並將 promise
的值做爲參數傳遞進去。 若是 promise
已經失敗,那麼執行 onRejected
, 並將 promise
失敗的緣由做爲參數傳遞進去。若是 promise
的狀態是 pending
,須要將 onFulfilled
和 onRejected
函數存放起來,等待狀態肯定後,再依次將對應的函數執行。then
的參數 onFulfilled
和 onRejected
能夠缺省promise
能夠 then
屢次,promise
的 then
方法返回一個新的 promise
then
返回的是一個結果,那麼就會把這個結果做爲參數,傳遞給下一個 then
的成功的回調 onFulfilled
then
中拋出了異常,那麼就會把這個異常做爲參數,傳遞給下一個 then
的失敗的回調 onRejected
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
安裝async
npm install promises-aplus-tests --save-dev
經過執行 promises-aplus-tests target.js
能夠執行測試,promises-aplus-tests
中共有 872 條測試用例。
能夠在個人 github 進行查看,經過 npm run test
來執行測試,能夠經過全部的測試用例。
更多精彩內容,歡迎關注微信公衆號~