在上一篇博客[[譯]前端基礎知識儲備——Promise/A+規範](https://segmentfault.com/a/11...,咱們介紹了Promise/A+規範的具體條目。在本文中,咱們來選擇了promiz,讓你們來看下一個具體的Promise庫的內部代碼是如何運做的。javascript
promiz是一個體積很小的promise庫(官方介紹約爲913 bytes (gzip)),做爲一個ES2015標準中的Promise的polyfill,實現了諸如resolve
、all
和race
等API。前端
咱們在這裏簡單回顧一下Promise/A+的主要關鍵點,若是須要了解詳細內容的同窗,能夠閱讀個人上一篇博客。java
pending
、fulfilled
和rejected
,且只能從pending
到fulfilled
或者rejected
,沒有其餘的流轉方式。then
函數的兩個回調函數,有且僅有一次機會被執行(即執行了onfulfilled
就不會執行onrejected
函數,且只執行一次)。在介紹Promise以前,咱們先介紹一下異步執行器。在Promise中,咱們須要一個異步的執行器來異步執行咱們的回調函數。在規範中提到,一般狀況下,咱們可使用微任務(nextTick)或者宏任務(setTimeout)來實現。可是,若是咱們須要兼容Web Worker這種狀況的話,咱們可能還須要一些更多的方式來處理。具體代碼以下:git
var queueId = 1 var queue = {} var isRunningTask = false // 使用postMessage來執行異步函數 if (!global.setImmediate) global.addEventListener('message', function (e) { if (e.source == global) { if (isRunningTask) nextTick(queue[e.data]) else { isRunningTask = true try { queue[e.data]() } catch (e) {} delete queue[e.data] isRunningTask = false } } }) /** * 異步執行方法 * @param {function} fn 須要執行的回調函數 */ function nextTick(fn) { if (global.setImmediate) setImmediate(fn) // 若是在Web Worker中使用如下方法 else if (global.importScripts) setTimeout(fn) else { queueId++ queue[queueId] = fn global.postMessage(queueId, '*') } }
以上代碼比較簡單,咱們簡單說明下:github
在代碼中,promiz使用了setImmediate
、setTimeout
和postMessage
這三個方法來執行異步函數,其中:segmentfault
setImmedeate
,只有IE實現了該方法,在執行完隊列中的代碼後當即執行。PostMessage
,新增的H5中的方法。setTimeout
,兼容性最佳,能夠適用各類場景。所以,在promiz的這段代碼中,有必定的兼容性問題,應該把setTimeout放到最後做爲一個兜底策略,不然沒法在老瀏覽器中執行。數組
說完了異步函數執行器,咱們來看下promise的構造函數。promise
首先咱們來看下內存數據,咱們須要存儲當前promise的狀態、成功的值或者失敗的緣由、下一個promise的引用和成功與失敗的回調函數。所以,咱們須要如下變量:瀏覽器
// states // 0: pending // 1: resolving // 2: rejecting // 3: resolved // 4: rejected var self = this, state = 0, // promise狀態 val = 0, // success callback返回值 next = [], // 返回的新的promise對象 fn, er; // then方法中的成功回調函數和失敗回調函數
在存儲完相關數據後,咱們來看下構造函數。異步
function Deferred(resolver) { ... self = this; try { if (typeof resolver == 'function') resolver(self['resolve'], self['reject']) } catch (e) { self['reject'](e) } }
構造函數很是簡單,除了聲明相關的函數,就只有執行傳入的callback而已。固然,若是咱們不是鏈式調用的第一個promise,那麼咱們會沒有resolver
參數,所以不須要在此執行,咱們會在then
函數執行resolve
方法。
下面咱們來看下上面提到的處理函數resovle
和reject
。
self['resolve'] = function (v) { fn = self.fn er = self.er if (!state) { val = v state = 1 nextTick(fire) } return self } self['reject'] = function (v) { fn = self.fn er = self.er if (!state) { val = v state = 2 nextTick(fire) } return self } self['then'] = function (_fn, _er) { if (!(this._d == 1)) throw TypeError() var d = new Deferred() d.fn = _fn d.er = _er if (state == 3) { d.resolve(val) } else if (state == 4) { d.reject(val) } else { next.push(d) } return d }
在resolve
和reject
這兩個函數中,都是改變了內部promise的狀態,給定了參數值,同時異步觸發了fire函數。而then
方法,則是生成了一個新的Deferred
對象,而且完成了相關的初始化(執行完then方法咱們就會獲得這個新生成的Deferred
對象,也就是一個新的Promise);當前一個promise到達resolved
狀態時,不須要等待則直接出發resolve方法,rejected
狀態時也同樣。那麼,讓咱們來看下fire方法究竟是作什麼的呢?
function fire() { // 檢測是否是一個thenable對象 var ref; try { ref = val && val.then } catch (e) { val = e state = 2 return fire() } thennable(ref, function () { state = 1 fire() }, function () { state = 2 fire() }, function () { try { if (state == 1 && typeof fn == 'function') { val = fn(val) } else if (state == 2 && typeof er == 'function') { val = er(val) state = 1 } } catch (e) { val = e return finish() } if (val == self) { val = TypeError() finish() } else thennable(ref, function () { finish(3) }, finish, function () { finish(state == 1 && 3) }) }) }
從上面的代碼來看,fire函數只是判斷了ref是否是一個thenable對象,而後調用了thenable函數,傳遞了3個回調函數。那麼這些回調函數究竟是作什麼用的呢?咱們須要來看下thenable函數的實現代碼。
// ref:指向thenable對象的`then`函數 // cb, ec, cn : successCallback, failureCallback, notThennableCallback function thennable(ref, cb, ec, cn) { // Promises can be rejected with other promises, which should pass through if (state == 2) { return cn() } if ((typeof val == 'object' || typeof val == 'function') && typeof ref == 'function') { try { // cnt變量用來保證成功和失敗的回調函數總共只會被執行一次 var cnt = 0 ref.call(val, function (v) { if (cnt++) return val = v cb() }, function (v) { if (cnt++) return val = v ec() }) } catch (e) { val = e ec() } } else { cn() } };
在thenable函數中,若是判斷當前的promise的狀態是處於rejecting
時,會直接執行cn
,也就是將reject狀態傳遞下去。而若是當ref不是一個thenable對象的then
函數時(那麼此時值爲undefined),那麼就會直接執行cn
。
經過fire函數傳遞的三個callback咱們能夠看到,cn
是在promise的狀態改變時,針對特定的狀態來觸發相對應的onfulfilled
或者onrejected
回調函數。
只有當ref
是一個thenable
時(傳遞給resolve
的是一個promise),代碼纔會進入上面的try catch
邏輯中。
看完了上面的各部分代碼,我相信你們可能對整個執行流程仍然不夠熟悉,下面,咱們將這些流程拼接起來,經過幾個完整的流程來講明下。
當咱們聲明一個promise式,咱們會傳入一個resolver
。此時,整個Deferred
對象的state
是0。若是咱們在resolver
裏面調用了resolve
方法,那麼咱們的state
就會變成1,而後出發fire函數註冊到thenable函數裏面的第三個回調函數,從而將值傳遞給下一個thenable。當thenable的then函數執行完成(即咱們看到的Promise後面跟着的then函數執行完成之後),咱們的state
纔會變成3,也就是說上一個Promise纔會結束,返回一個新的Promise。
若是不是第一個Promise,那麼咱們就沒有resolver
參數。所以,咱們的resolve
方法並非經過在resolver
中進行調用的,而是將回調函數fn
註冊進來,在上一個Promise完成後主動調用執行的。也就是說,咱們在上一個Promise執行完then函數而且返回一個新的Promise時,咱們這個返回的Promise就已經進入了resolving
的狀態。
resolve
傳遞一個Promise在Promise/A+規範中,若是咱們給resolve
傳遞一個promise,那麼咱們的經過resolve
獲取到的值就是傳遞進去的這個promise返回的值。固然,咱們也必須等待做爲參數的這個promise處理完成後,纔會處理外面的這個promise。
在promiz的代碼中,咱們若是經過resolve
接收到一個promise,那麼咱們在fire函數中就會吧promise.then
的引用傳遞給thenable
函數。在thenable
函數中,咱們會將咱們當前promise須要執行的onfulfilled
和onrejected
封裝成一個函數,傳遞給做爲參數的promise的then函數。所以,看成爲參數的promise執行任意結果的回調函數時,就會將參數傳遞給外層的promise,執行對應的回調函數。
讓咱們先看代碼。
Deferred.all = function (arr) { if (!(this._d == 1)) throw TypeError() if (!(arr instanceof Array)) return Deferred.reject(TypeError()) var d = new Deferred() function done(e, v) { if (v) return d.resolve(v) if (e) return d.reject(e) var unresolved = arr.reduce(function (cnt, v) { if (v && v.then) return cnt + 1 return cnt }, 0) if (unresolved == 0) d.resolve(arr) arr.map(function (v, i) { if (v && v.then) v.then(function (r) { arr[i] = r done() return r }, done) }) } done() return d }
在Promise.all
中,咱們使用了一個計數器來進行統計,在每個Promise後面都增長一個then函數用於增長計數。當Promise成功時則計數+1。當整個數組中的Promise都已經進入resolved
狀態時,咱們纔會執行thenable
的then函數。若是有一個失敗的話,則當即進入reject流程。
從代碼設計層面來看,promiz的代碼量較少,閱讀也較爲簡單。可是,在某些細節的設計上,promiz仍是體現出了較爲巧妙的思路,如在處理做爲入參的promise時,可以在這個promise後面動態的添加一個then
函數,從而獲取數據給外面的promise。
若是你們有興趣,建議本身根據本文的說明閱讀一遍源碼,配合Promise/A+規範來看下是如何實現每一條規範的。
下一篇博客,咱們將爲你們從頭開始,來實現一個Promise庫。