摘要:因此,當(dāng)作為參數(shù)的執(zhí)行任意結(jié)果的回調(diào)函數(shù)時(shí),就會(huì)將參數(shù)傳遞給外層的,執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。
背景
在上一篇博客[[譯]前端基礎(chǔ)知識(shí)儲(chǔ)備——Promise/A+規(guī)范](https://segmentfault.com/a/11...,我們介紹了Promise/A+規(guī)范的具體條目。在本文中,我們來(lái)選擇了promiz,讓大家來(lái)看下一個(gè)具體的Promise庫(kù)的內(nèi)部代碼是如何運(yùn)作的。
promiz是一個(gè)體積很小的promise庫(kù)(官方介紹約為913 bytes (gzip)),作為一個(gè)ES2015標(biāo)準(zhǔn)中的Promise的polyfill,實(shí)現(xiàn)了諸如resolve、all和race等API。
知識(shí)儲(chǔ)備我們?cè)谶@里簡(jiǎn)單回顧一下Promise/A+的主要關(guān)鍵點(diǎn),如果需要了解詳細(xì)內(nèi)容的同學(xué),可以閱讀我的上一篇博客。
Promise有三個(gè)狀態(tài),分別為pending、fulfilled和rejected,且只能從pending到fulfilled或者rejected,沒(méi)有其他的流轉(zhuǎn)方式。
Promise的返回值是一個(gè)新的Promise,原因見(jiàn)上一條。
傳遞給then函數(shù)的兩個(gè)回調(diào)函數(shù),有且僅有一次機(jī)會(huì)被執(zhí)行(即執(zhí)行了onfulfilled就不會(huì)執(zhí)行onrejected函數(shù),且只執(zhí)行一次)。
代碼實(shí)現(xiàn)與分析 異步執(zhí)行器在介紹Promise之前,我們先介紹一下異步執(zhí)行器。在Promise中,我們需要一個(gè)異步的執(zhí)行器來(lái)異步執(zhí)行我們的回調(diào)函數(shù)。在規(guī)范中提到,通常情況下,我們可以使用微任務(wù)(nextTick)或者宏任務(wù)(setTimeout)來(lái)實(shí)現(xiàn)。但是,如果我們需要兼容Web Worker這種情況的話,我們可能還需要一些更多的方式來(lái)處理。具體代碼如下:
var queueId = 1 var queue = {} var isRunningTask = false // 使用postMessage來(lái)執(zhí)行異步函數(shù) 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 } } }) /** * 異步執(zhí)行方法 * @param {function} fn 需要執(zhí)行的回調(diào)函數(shù) */ function nextTick(fn) { if (global.setImmediate) setImmediate(fn) // 如果在Web Worker中使用以下方法 else if (global.importScripts) setTimeout(fn) else { queueId++ queue[queueId] = fn global.postMessage(queueId, "*") } }
以上代碼比較簡(jiǎn)單,我們簡(jiǎn)單說(shuō)明下:
在代碼中,promiz使用了setImmediate、setTimeout和postMessage這三個(gè)方法來(lái)執(zhí)行異步函數(shù),其中:
setImmedeate,只有IE實(shí)現(xiàn)了該方法,在執(zhí)行完隊(duì)列中的代碼后立即執(zhí)行。
PostMessage,新增的H5中的方法。
setTimeout,兼容性最佳,可以適用各種場(chǎng)景。
因此,在promiz的這段代碼中,有一定的兼容性問(wèn)題,應(yīng)該把setTimeout放到最后作為一個(gè)兜底策略,否則無(wú)法在老瀏覽器中執(zhí)行。
構(gòu)造函數(shù)說(shuō)完了異步函數(shù)執(zhí)行器,我們來(lái)看下promise的構(gòu)造函數(shù)。
首先我們來(lái)看下內(nèi)存數(shù)據(jù),我們需要存儲(chǔ)當(dāng)前promise的狀態(tài)、成功的值或者失敗的原因、下一個(gè)promise的引用和成功與失敗的回調(diào)函數(shù)。因此,我們需要以下變量:
// states // 0: pending // 1: resolving // 2: rejecting // 3: resolved // 4: rejected var self = this, state = 0, // promise狀態(tài) val = 0, // success callback返回值 next = [], // 返回的新的promise對(duì)象 fn, er; // then方法中的成功回調(diào)函數(shù)和失敗回調(diào)函數(shù)
在存儲(chǔ)完相關(guān)數(shù)據(jù)后,我們來(lái)看下構(gòu)造函數(shù)。
function Deferred(resolver) { ... self = this; try { if (typeof resolver == "function") resolver(self["resolve"], self["reject"]) } catch (e) { self["reject"](e) } }
構(gòu)造函數(shù)非常簡(jiǎn)單,除了聲明相關(guān)的函數(shù),就只有執(zhí)行傳入的callback而已。當(dāng)然,如果我們不是鏈?zhǔn)秸{(diào)用的第一個(gè)promise,那么我們會(huì)沒(méi)有resolver參數(shù),因此不需要在此執(zhí)行,我們會(huì)在then函數(shù)執(zhí)行resolve方法。
下面我們來(lái)看下上面提到的處理函數(shù)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這兩個(gè)函數(shù)中,都是改變了內(nèi)部promise的狀態(tài),給定了參數(shù)值,同時(shí)異步觸發(fā)了fire函數(shù)。而then方法,則是生成了一個(gè)新的Deferred對(duì)象,并且完成了相關(guān)的初始化(執(zhí)行完then方法我們就會(huì)得到這個(gè)新生成的Deferred對(duì)象,也就是一個(gè)新的Promise);當(dāng)前一個(gè)promise到達(dá)resolved狀態(tài)時(shí),不需要等待則直接出發(fā)resolve方法,rejected狀態(tài)時(shí)也一樣。那么,讓我們來(lái)看下fire方法到底是做什么的呢?
function fire() { // 檢測(cè)是不是一個(gè)thenable對(duì)象 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) }) }) }
從上面的代碼來(lái)看,fire函數(shù)只是判斷了ref是不是一個(gè)thenable對(duì)象,然后調(diào)用了thenable函數(shù),傳遞了3個(gè)回調(diào)函數(shù)。那么這些回調(diào)函數(shù)到底是做什么用的呢?我們需要來(lái)看下thenable函數(shù)的實(shí)現(xiàn)代碼。
// ref:指向thenable對(duì)象的`then`函數(shù) // 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變量用來(lái)保證成功和失敗的回調(diào)函數(shù)總共只會(huì)被執(zhí)行一次 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函數(shù)中,如果判斷當(dāng)前的promise的狀態(tài)是處于rejecting時(shí),會(huì)直接執(zhí)行cn,也就是將reject狀態(tài)傳遞下去。而如果當(dāng)ref不是一個(gè)thenable對(duì)象的then函數(shù)時(shí)(那么此時(shí)值為undefined),那么就會(huì)直接執(zhí)行cn。
通過(guò)fire函數(shù)傳遞的三個(gè)callback我們可以看到,cn是在promise的狀態(tài)改變時(shí),針對(duì)特定的狀態(tài)來(lái)觸發(fā)相對(duì)應(yīng)的onfulfilled或者onrejected回調(diào)函數(shù)。
只有當(dāng)ref是一個(gè)thenable時(shí)(傳遞給resolve的是一個(gè)promise),代碼才會(huì)進(jìn)入上面的try catch邏輯中。
Promise執(zhí)行流程看完了上面的各部分代碼,我相信大家可能對(duì)整個(gè)執(zhí)行流程仍然不夠熟悉,下面,我們將這些流程拼接起來(lái),通過(guò)幾個(gè)完整的流程來(lái)說(shuō)明下。
鏈?zhǔn)秸{(diào)用第一個(gè)Promise當(dāng)我們聲明一個(gè)promise式,我們會(huì)傳入一個(gè)resolver。此時(shí),整個(gè)Deferred對(duì)象的state是0。如果我們?cè)?b>resolver里面調(diào)用了resolve方法,那么我們的state就會(huì)變成1,然后出發(fā)fire函數(shù)注冊(cè)到thenable函數(shù)里面的第三個(gè)回調(diào)函數(shù),從而將值傳遞給下一個(gè)thenable。當(dāng)thenable的then函數(shù)執(zhí)行完成(即我們看到的Promise后面跟著的then函數(shù)執(zhí)行完成以后),我們的state才會(huì)變成3,也就是說(shuō)上一個(gè)Promise才會(huì)結(jié)束,返回一個(gè)新的Promise。
鏈?zhǔn)秸{(diào)用非第一個(gè)Promise如果不是第一個(gè)Promise,那么我們就沒(méi)有resolver參數(shù)。因此,我們的resolve方法并不是通過(guò)在resolver中進(jìn)行調(diào)用的,而是將回調(diào)函數(shù)fn注冊(cè)進(jìn)來(lái),在上一個(gè)Promise完成后主動(dòng)調(diào)用執(zhí)行的。也就是說(shuō),我們?cè)谏弦粋€(gè)Promise執(zhí)行完then函數(shù)并且返回一個(gè)新的Promise時(shí),我們這個(gè)返回的Promise就已經(jīng)進(jìn)入了resolving的狀態(tài)。
給resolve傳遞一個(gè)Promise在Promise/A+規(guī)范中,如果我們給resolve傳遞一個(gè)promise,那么我們的通過(guò)resolve獲取到的值就是傳遞進(jìn)去的這個(gè)promise返回的值。當(dāng)然,我們也必須等待作為參數(shù)的這個(gè)promise處理完成后,才會(huì)處理外面的這個(gè)promise。
在promiz的代碼中,我們?nèi)绻ㄟ^(guò)resolve接收到一個(gè)promise,那么我們?cè)趂ire函數(shù)中就會(huì)吧promise.then的引用傳遞給thenable函數(shù)。在thenable函數(shù)中,我們會(huì)將我們當(dāng)前promise需要執(zhí)行的onfulfilled和onrejected封裝成一個(gè)函數(shù),傳遞給作為參數(shù)的promise的then函數(shù)。因此,當(dāng)作為參數(shù)的promise執(zhí)行任意結(jié)果的回調(diào)函數(shù)時(shí),就會(huì)將參數(shù)傳遞給外層的promise,執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。
全局執(zhí)行方法 Promise.all讓我們先看代碼。
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中,我們使用了一個(gè)計(jì)數(shù)器來(lái)進(jìn)行統(tǒng)計(jì),在每一個(gè)Promise后面都增加一個(gè)then函數(shù)用于增加計(jì)數(shù)。當(dāng)Promise成功時(shí)則計(jì)數(shù)+1。當(dāng)整個(gè)數(shù)組中的Promise都已經(jīng)進(jìn)入resolved狀態(tài)時(shí),我們才會(huì)執(zhí)行thenable的then函數(shù)。如果有一個(gè)失敗的話,則立即進(jìn)入reject流程。
總結(jié)從代碼設(shè)計(jì)層面來(lái)看,promiz的代碼量較少,閱讀也較為簡(jiǎn)單。但是,在某些細(xì)節(jié)的設(shè)計(jì)上,promiz還是體現(xiàn)出了較為巧妙的思路,如在處理作為入?yún)⒌膒romise時(shí),能夠在這個(gè)promise后面動(dòng)態(tài)的添加一個(gè)then函數(shù),從而獲取數(shù)據(jù)給外面的promise。
如果大家有興趣,建議自己根據(jù)本文的說(shuō)明閱讀一遍源碼,配合Promise/A+規(guī)范來(lái)看下是如何實(shí)現(xiàn)每一條規(guī)范的。
下一篇博客,我們將為大家從頭開(kāi)始,來(lái)實(shí)現(xiàn)一個(gè)Promise庫(kù)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/98911.html
摘要:寫(xiě)在最后總體來(lái)說(shuō),是一個(gè)小而美的框架,值得我們來(lái)折騰一下,以上均為本人理解,如有錯(cuò)誤還請(qǐng)指出,不勝感激一個(gè)硬廣我所在團(tuán)隊(duì)工作地點(diǎn)在北京求大量前端社招實(shí)習(xí),有意者可發(fā)簡(jiǎn)歷至 寫(xiě)在前面 沒(méi)錯(cuò),又是一個(gè)新的前端框架,hyperapp非常的小,僅僅1kb,當(dāng)然學(xué)習(xí)起來(lái)也是非常的簡(jiǎn)單,可以說(shuō)是1分鐘入門(mén)。聲明式:HyperApp 的設(shè)計(jì)基于Elm Architecture(這也意味著組件更多的是...
摘要:專有的內(nèi)容更少,而更多符合標(biāo)準(zhǔn)的成分。當(dāng)前標(biāo)簽實(shí)例的方法被調(diào)用時(shí)當(dāng)前標(biāo)簽的任何一個(gè)祖先的被調(diào)用時(shí)更新從父親到兒子單向傳播。相對(duì)來(lái)說(shuō),微型場(chǎng)景會(huì)更適合,不想要太多的外部依賴,又需要組件化數(shù)據(jù)驅(qū)動(dòng)等更現(xiàn)代化框架的能力。 Riot.js是什么? Riot 擁有創(chuàng)建現(xiàn)代客戶端應(yīng)用的所有必需的成分: 響應(yīng)式 視圖層用來(lái)創(chuàng)建用戶界面 用來(lái)在各獨(dú)立模塊之間進(jìn)行通信的事件庫(kù) 用來(lái)管理URL和瀏覽器回...
摘要:專有的內(nèi)容更少,而更多符合標(biāo)準(zhǔn)的成分。當(dāng)前標(biāo)簽實(shí)例的方法被調(diào)用時(shí)當(dāng)前標(biāo)簽的任何一個(gè)祖先的被調(diào)用時(shí)更新從父親到兒子單向傳播。相對(duì)來(lái)說(shuō),微型場(chǎng)景會(huì)更適合,不想要太多的外部依賴,又需要組件化數(shù)據(jù)驅(qū)動(dòng)等更現(xiàn)代化框架的能力。 Riot.js是什么? Riot 擁有創(chuàng)建現(xiàn)代客戶端應(yīng)用的所有必需的成分: 響應(yīng)式 視圖層用來(lái)創(chuàng)建用戶界面 用來(lái)在各獨(dú)立模塊之間進(jìn)行通信的事件庫(kù) 用來(lái)管理URL和瀏覽器回...
摘要:這部分比較容易讓人產(chǎn)生疑惑的是循環(huán)部分,這個(gè)循環(huán)有什么用呢舉個(gè)例子以上代碼是最簡(jiǎn)單的情況,監(jiān)聽(tīng)事件,當(dāng)變化時(shí),打印出在源碼中,當(dāng)?shù)谝淮芜M(jìn)入后,緊接著被置為,而事件觸發(fā)回調(diào)函數(shù)也不會(huì)更改的值,因此再次判斷時(shí)條件不成立,內(nèi)的代碼段只會(huì)執(zhí)行一次。 本文已同步在我的博客 在這個(gè)react和vue如日中天、jquery逐漸被大家拋棄的年代,我還是想要來(lái)說(shuō)一說(shuō)backbone。 16年6月初,在沒(méi)...
摘要:主張,小而美被實(shí)踐是最好用的,本文將介紹筆者收集的一些非常贊的開(kāi)源庫(kù)。是帶有消息通知的數(shù)據(jù)中心,我稱其為會(huì)說(shuō)話的數(shù)據(jù)。迷你檢查庫(kù),這個(gè)幾乎涵蓋了全部的各種檢測(cè)。最后向大家推薦依稀,這里收集了太多小而美的庫(kù),自己來(lái)淘寶吧。 最近看著下自己的github star,把我嚇壞了,手賤黨,收藏癖的我都收藏了300+個(gè)倉(cāng)庫(kù)了,是時(shí)候整理一下了。 Unix主張kiss,小而美被實(shí)踐是最好用的,本文...
閱讀 2568·2021-11-22 13:53
閱讀 4069·2021-09-28 09:47
閱讀 858·2021-09-22 15:33
閱讀 809·2020-12-03 17:17
閱讀 3315·2019-08-30 13:13
閱讀 2121·2019-08-29 16:09
閱讀 1176·2019-08-29 12:24
閱讀 2452·2019-08-28 18:14