這兩天在熟悉 kissy 框架的時候,看到了
Promise
模塊。Promise
對於一個Jser並不陌生,Promise
類似於一個事務管理器,它的作用就是將各種內嵌回調的事務用流水形式表達。利用Promise
可以讓異步編程更符合人的直覺,讓代碼邏輯更加清晰,把開發人員從回調地獄中釋放出來。這么“高大上”的東西,以前寫nodejs
代碼的時候只是簡單的用用,還沒有理解其基本的實現原理,罪過!個人認為,理解編程思想最好的途徑就是閱讀一份簡易的實現源碼。很幸運,網上有不少Promise
的簡易實現,其中 這篇博文 介紹的實現方式非常贊,下面就來好好研究下吧!
基礎概念
目前, Promise
是 ECMAScript 6
規范的重要特性之一,各大瀏覽器也開始慢慢支持這一特性。當然,也有一些第三方內庫實現了該功能,如: Q 、 when 、 WinJS 、 RSVP.js 等。
Promise
對象用來進行延遲( deferred
)和異步( asynchronous
)計算。一個 Promise
處於以下四種狀態之一:
- pending: 還沒有得到肯定或者失敗結果,進行中
- fulfilled: 成功的操作
- rejected: 失敗的操作
- settled: 已被
fulfilled
或rejected
Promise
對象有兩個重要的方法,一個是 then
,另一個是 resolve
:
- then:將事務添加到事務隊列中
- resolve:開啟流程,讓整個操作從第一個事務開始執行
Promise
常用方式如下:
var p = new Promise(function(resolve, reject) { ... // 事務觸發 resovle(xxx); ... }); p.then(function(value) { // 滿足 }, function(reason) { // 拒絕 }).then().then()...
示意圖如下:
實現步驟
1. Promise
其實就是一個狀態機。按照它的定義,我們可從如下基礎代碼開始:
var PENDING = 0; // 進行中 var FULFILLED = 1; // 成功 var REJECTED = 2; // 失敗 function Promise() { // 存儲PENDING, FULFILLED或者REJECTED的狀態 var state = PENDING; // 存儲成功或失敗的結果值 var value = null; // 存儲成功或失敗的處理程序,通過調用`.then`或者`.done`方法 var handlers = []; // 成功狀態變化 function fulfill(result) { state = FULFILLED; value = result; } // 失敗狀態變化 function reject(error) { state = REJECTED; value = error; } }
2.下面是 Promise
的 resolve
方法實現:
注意: resolve
方法可接收的參數有兩種:一個普通的值/對象或者一個 Promise
對象。如果是普通的值/對象,則直接把結果傳遞到下一個對象;如果是一個 Promise
對象,則必須先等待這個子任務序列完成。
function Promise() { ... function resolve(result) { try { var then = getThen(result); // 如果是一個promise對象 if (then) { doResolve(then.bind(result), resolve, reject); return; } // 修改狀態,傳遞結果到下一個事務 fulfill(result); } catch (e) { reject(e); } } }
兩個輔助方法:
/** * Check if a value is a Promise and, if it is, * return the `then` method of that promise. * * @param {Promise|Any} value * @return {Function|Null} */ function getThen(value) { var t = typeof value; if (value && (t === 'object' || t === 'function')) { var then = value.then; if (typeof then === 'function') { return then; } } return null; } /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. * * @param {Function} fn A resolver function that may not be trusted * @param {Function} onFulfilled * @param {Function} onRejected */ function doResolve(fn, onFulfilled, onRejected) { var done = false; try { fn(function(value) { if (done) return; done = true; onFulfilled(value); }, function(reason) { if (done) return; done = true; onRejected(reason); }); } catch(ex) { if (done) return; done = true; onRejected(ex); } }
3.上面已經完成了一個完整的內部狀態機,但我們並沒有暴露一個方法去解析或則觀察 Promise
。現在讓我們開始解析 Promise
:
function Promise(fn) { ... doResolve(fn, resolve, reject); }
如你所見,我們復用了 doResolve
,因為對於初始化的 fn
也要對其進行控制。 fn
允許調用 resolve
或則 reject
多次,甚至拋出異常。這完全取決於我們去保證 promise
對象僅被 resolved
或則 rejected
一次,且狀態不能隨意改變。
4.目前,我們已經有了一個完整的狀態機,但我們仍然沒有辦法去觀察它的任何變化。我們最終的目標是實現 then
方法,但 done
方法似乎更簡單,所以讓我們先實現它。
我們的目標是實現 promise.done(onFullfilled, onRejected)
:
onFulfilled
和onRejected
兩者只能有一個被執行,且執行次數為一- 該方法僅能被調用一次
- 一旦調用了該方法,則
promise
鏈式調用結束 - 無論是否
promise
已經被解析,都可以調用該方法
var PENDING = 0; // 進行中 var FULFILLED = 1; // 成功 var REJECTED = 2; // 失敗 function Promise() { // 存儲PENDING, FULFILLED或者REJECTED的狀態 var state = PENDING; // 存儲成功或失敗的結果值 var value = null; // 存儲成功或失敗的處理程序,通過調用`.then`或者`.done`方法 var handlers = []; // 成功狀態變化 function fulfill(result) { state = FULFILLED; value = result; handlers.forEach(handle); handlers = null; } // 失敗狀態變化 function reject(error) { state = REJECTED; value = error; handlers.forEach(handle); handlers = null; } function resolve(result) { try { var then = getThen(result); if (then) { doResolve(then.bind(result), resolve, reject) return } fulfill(result); } catch (e) { reject(e); } } // 不同狀態,進行不同的處理 function handle(handler) { if (state === PENDING) { handlers.push(handler); } else { if (state === FULFILLED && typeof handler.onFulfilled === 'function') { handler.onFulfilled(value); }