摘要:顯然,了解的實(shí)現(xiàn)細(xì)節(jié),可以幫助我們更好地應(yīng)用它。本文將主要根據(jù)的這篇文章,探討的實(shí)現(xiàn)細(xì)節(jié)。核心說(shuō)明盡管已經(jīng)有自己的規(guī)范,但目前的各類庫(kù),在的實(shí)現(xiàn)細(xì)節(jié)上是有差異的,部分甚至在意義上完全不同。到前面到為止,所實(shí)現(xiàn)的都是不能級(jí)聯(lián)的。
在之前的異步JavaScript與Promise一文中,我介紹了Promise以及它在異步JavaScript中的使用意義。一般來(lái)說(shuō),我們是通過(guò)各種JavaScript庫(kù)來(lái)應(yīng)用Promise的。隨著使用Promise的機(jī)會(huì)越來(lái)越多,你也可能像我這樣會(huì)關(guān)心Promise到底是如何工作的。顯然,了解Promise的實(shí)現(xiàn)細(xì)節(jié),可以幫助我們更好地應(yīng)用它。尤其是碰到一些Promise的問(wèn)題時(shí),也許可以更快速、更準(zhǔn)確地定位原因,并解決它。
非常慶幸,在[Promises/A wiki][]中位于庫(kù)列表第一位的[Q][],提供了它作為一個(gè)Promise庫(kù)的[基本設(shè)計(jì)原理解析][]。本文將主要根據(jù)Q的這篇文章,探討Promise的實(shí)現(xiàn)細(xì)節(jié)。
Promise核心說(shuō)明盡管Promise已經(jīng)有自己的規(guī)范,但目前的各類Promise庫(kù),在Promise的實(shí)現(xiàn)細(xì)節(jié)上是有差異的,部分API甚至在意義上完全不同。但Promise的核心內(nèi)容,是相通的,它就是then方法。在相關(guān)術(shù)語(yǔ)中,promise指的就是一個(gè)有then方法,且該方法能觸發(fā)特定行為的對(duì)象或函數(shù)。
有關(guān)Promise核心說(shuō)明的細(xì)節(jié),推薦閱讀[Promises/A+][]。這篇文章是寫(xiě)給Promise庫(kù)的開(kāi)發(fā)者的,你可以找到各種對(duì)Promise特性的說(shuō)明。[Promises/A+][]希望開(kāi)發(fā)者遵從這些特性,以實(shí)現(xiàn)可以共同使用的Promise(也就是說(shuō),不同的Promise庫(kù)也可共用)。
Promise可以有不同的實(shí)現(xiàn)方式,因此Promise核心說(shuō)明并不會(huì)討論任何具體的實(shí)現(xiàn)代碼。
先閱讀Promise核心說(shuō)明的意思是:看,這就是需要寫(xiě)出來(lái)的結(jié)果,請(qǐng)參照這個(gè)結(jié)果想一想怎么用代碼寫(xiě)出來(lái)吧。
起步:用這一種方式理解Promise回想一下Promise解決的是什么問(wèn)題?回調(diào)。例如,函數(shù)doMission1()代表第一件事情,現(xiàn)在,我們想要在這件事情完成后,再做下一件事情doMission2(),應(yīng)該怎么做呢?
先看看我們常見(jiàn)的回調(diào)模式。doMission1()說(shuō):“你要這么做的話,就把doMission2()交給我,我在結(jié)束后幫你調(diào)用?!彼詴?huì)是:
doMission1(doMission2);
Promise模式又是如何呢?你對(duì)doMission1()說(shuō):“不行,控制權(quán)要在我這里。你應(yīng)該改變一下,你先返回一個(gè)特別的東西給我,然后我來(lái)用這個(gè)東西安排下一件事?!边@個(gè)特別的東西就是Promise,這會(huì)變成這樣:
doMission1().then(doMission2);
可以看出,Promise將回調(diào)模式的主從關(guān)系調(diào)換了一個(gè)位置(翻身做主人!),多個(gè)事件的流程關(guān)系,就可以這樣集中到主干道上(而不是分散在各個(gè)事件函數(shù)之內(nèi))。
好了,如何做這樣一個(gè)轉(zhuǎn)換呢?從最簡(jiǎn)單的情況來(lái)吧,假定doMission1()的代碼是:
function doMission1(callback){ var value = 1; callback(value); }
那么,它可以改變一下,變成這樣:
function doMission1(){ return { then: function(callback){ var value = 1; callback(value); } }; }
這就完成了轉(zhuǎn)換。雖然并不是實(shí)際有用的轉(zhuǎn)換,但到這里,其實(shí)已經(jīng)觸及了Promise最為重要的實(shí)現(xiàn)要點(diǎn),即Promise將返回值轉(zhuǎn)換為帶then方法的對(duì)象。
進(jìn)階:Q的設(shè)計(jì)路程 從defer開(kāi)始design/q0.js是Q初步成型的第一步。它創(chuàng)建了一個(gè)名為defer的工具函數(shù),用于創(chuàng)建Promise:
var defer = function () { var pending = [], value; return { resolve: function (_value) { value = _value; for (var i = 0, ii = pending.length; i < ii; i++) { var callback = pending[i]; callback(value); } pending = undefined; }, then: function (callback) { if (pending) { pending.push(callback); } else { callback(value); } } } };
這段源碼可以看出,運(yùn)行defer()將得到一個(gè)對(duì)象,該對(duì)象包含resolve和then兩個(gè)方法。請(qǐng)回想一下jQuery的Deferred(同樣有resolve和then),這兩個(gè)方法將會(huì)是近似的效果。then會(huì)參考pending的狀態(tài),如果是等待狀態(tài)則將回調(diào)保存(push),否則立即調(diào)用回調(diào)。resolve則將肯定這個(gè)Promise,更新值的同時(shí)運(yùn)行完所有保存的回調(diào)。defer的使用示例如下:
var oneOneSecondLater = function () { var result = defer(); setTimeout(function () { result.resolve(1); }, 1000); return result; }; oneOneSecondLater().then(callback);
這里oneOneSecondLater()包含異步內(nèi)容(setTimeout),但這里讓它立即返回了一個(gè)defer()生成的對(duì)象,然后將對(duì)象的resolve方法放在異步結(jié)束的位置調(diào)用(并附帶上值,或者說(shuō)結(jié)果)。
到此,以上代碼存在一個(gè)問(wèn)題:resolve可以被執(zhí)行多次。因此,resolve中應(yīng)該加入對(duì)狀態(tài)的判斷,保證resolve只有一次有效。這就是Q下一步的design/q1.js(僅差異部分):
resolve: function (_value) { if (pending) { value = _value; for (var i = 0, ii = pending.length; i < ii; i++) { var callback = pending[i]; callback(value); } pending = undefined; } else { throw new Error("A promise can only be resolved once."); } }
對(duì)第二次及更多的調(diào)用,可以這樣拋出一個(gè)錯(cuò)誤,也可以直接忽略掉。
分離defer和promise在前面的實(shí)現(xiàn)中,defer生成的對(duì)象同時(shí)擁有then方法和resolve方法。按照定義,promise關(guān)心的是then方法,至于觸發(fā)promise改變狀態(tài)的resolve,是另一回事。所以,Q接下來(lái)將擁有then方法的promise,和擁有resolve的defer分離開(kāi)來(lái),各自獨(dú)立使用。這樣就好像劃清了各自的職責(zé),各自只留一定的權(quán)限,這會(huì)使代碼邏輯更明晰,易于調(diào)整。請(qǐng)看design/q3.js:(q2在此跳過(guò))
var isPromise = function (value) { return value && typeof value.then === "function"; }; var defer = function () { var pending = [], value; return { resolve: function (_value) { if (pending) { value = _value; for (var i = 0, ii = pending.length; i < ii; i++) { var callback = pending[i]; callback(value); } pending = undefined; } }, promise: { then: function (callback) { if (pending) { pending.push(callback); } else { callback(value); } } } }; };
如果你仔細(xì)對(duì)比一下q1,你會(huì)發(fā)現(xiàn)區(qū)別很小。一方面,不再拋出錯(cuò)誤(改為直接忽略第二次及更多的resolve),另一方面,將then方法移動(dòng)到一個(gè)名為promise的對(duì)象內(nèi)。到這里,運(yùn)行defer()得到的對(duì)象(就稱為defer吧),將擁有resolve方法,和一個(gè)promise屬性指向另一個(gè)對(duì)象。這另一個(gè)對(duì)象就是僅有then方法的promise。這就完成了分離。
前面還有一個(gè)isPromise()函數(shù),它通過(guò)是否有then方法來(lái)判斷對(duì)象是否是promise(duck-typing的判斷方法)。為了正確使用和處理分離開(kāi)的promise,會(huì)像這樣需要將promise和其他值區(qū)分開(kāi)來(lái)。
實(shí)現(xiàn)promise的級(jí)聯(lián)接下來(lái)會(huì)是相當(dāng)重要的一步。到前面到q3為止,所實(shí)現(xiàn)的promise都是不能級(jí)聯(lián)的。但你所熟知的promise應(yīng)該支持這樣的語(yǔ)法:
promise.then(step1).then(step2);
以上過(guò)程可以理解為,promise將可以創(chuàng)造新的promise,且取自舊的promise的值(前面代碼中的value)。要實(shí)現(xiàn)then的級(jí)聯(lián),需要做到一些事情:
then方法必須返回promise。
這個(gè)返回的promise必須用傳遞給then方法的回調(diào)運(yùn)行后的返回結(jié)果,來(lái)設(shè)置自己的值。
傳遞給then方法的回調(diào),必須返回一個(gè)promise或值。
design/q4.js中,為了實(shí)現(xiàn)這一點(diǎn),新增了一個(gè)工具函數(shù)ref:
var ref = function (value) { if (value && typeof value.then === "function") return value; return { then: function (callback) { return ref(callback(value)); } }; };
這是在著手處理與promise關(guān)聯(lián)的value。這個(gè)工具函數(shù)將對(duì)任一個(gè)value值做一次包裝,如果是一個(gè)promise,則什么也不做,如果不是promise,則將它包裝成一個(gè)promise。注意這里有一個(gè)遞歸,它確保包裝成的promise可以使用then方法級(jí)聯(lián)。為了幫助理解它,下面是一個(gè)使用的例子:
ref("step1").then(function(value){ console.log(value); // "step1" return 15; }).then(function(value){ console.log(value); // 15 });
你可以看到value是怎樣傳遞的,promise級(jí)聯(lián)需要做到的也是如此。
design/q4.js通過(guò)結(jié)合使用這個(gè)ref函數(shù),將原來(lái)的defer轉(zhuǎn)變?yōu)榭杉?jí)聯(lián)的形式:
var defer = function () { var pending = [], value; return { resolve: function (_value) { if (pending) { value = ref(_value); // values wrapped in a promise for (var i = 0, ii = pending.length; i < ii; i++) { var callback = pending[i]; value.then(callback); // then called instead } pending = undefined; } }, promise: { then: function (_callback) { var result = defer(); // callback is wrapped so that its return // value is captured and used to resolve the promise // that "then" returns var callback = function (value) { result.resolve(_callback(value)); }; if (pending) { pending.push(callback); } else { value.then(callback); } return result.promise; } } }; };
原來(lái)callback(value)的形式,都修改為value.then(callback)。這個(gè)修改后效果其實(shí)和原來(lái)相同,只是因?yàn)?b>value變成了promise包裝的類型,會(huì)需要這樣調(diào)用。
then方法有了較多變動(dòng),會(huì)先新生成一個(gè)defer,并在結(jié)尾處返回這個(gè)defer的promise。請(qǐng)注意,callback不再是直接取用傳遞給then的那個(gè),而是在此基礎(chǔ)之上增加一層,并把新生成的defer的resolve方法放置在此。此處可以理解為,then方法將返回一個(gè)新生成的promise,因此需要把promise的resolve也預(yù)留好,在舊的promise的resolve運(yùn)行后,新的promise的resolve也會(huì)隨之運(yùn)行。這樣才能像管道一樣,讓事件按照then連接的內(nèi)容,一層一層傳遞下去。
加入錯(cuò)誤處理promise的then方法應(yīng)該可以包含兩個(gè)參數(shù),分別是肯定和否定狀態(tài)的處理函數(shù)(onFulfilled與onRejected)。前面我們實(shí)現(xiàn)的promise還只能轉(zhuǎn)變?yōu)榭隙顟B(tài),所以,接下來(lái)應(yīng)該加入否定狀態(tài)部分。
請(qǐng)注意,promise的then方法的兩個(gè)參數(shù),都是可選參數(shù)。design/q6.js(q5也跳過(guò))加入了工具函數(shù)reject來(lái)幫助實(shí)現(xiàn)promise的否定狀態(tài):
var reject = function (reason) { return { then: function (callback, errback) { return ref(errback(reason)); } }; };
它和ref的主要區(qū)別是,它返回的對(duì)象的then方法,只會(huì)取第二個(gè)參數(shù)的errback來(lái)運(yùn)行。design/q6.js的其余部分是:
var defer = function () { var pending = [], value; return { resolve: function (_value) { if (pending) { value = ref(_value); for (var i = 0, ii = pending.length; i < ii; i++) { value.then.apply(value, pending[i]); } pending = undefined; } }, promise: { then: function (_callback, _errback) { var result = defer(); // provide default callbacks and errbacks _callback = _callback || function (value) { // by default, forward fulfillment return value; }; _errback = _errback || function (reason) { // by default, forward rejection return reject(reason); }; var callback = function (value) { result.resolve(_callback(value)); }; var errback = function (reason) { result.resolve(_errback(reason)); }; if (pending) { pending.push([callback, errback]); } else { value.then(callback, errback); } return result.promise; } } }; };
這里的主要改動(dòng)是,將數(shù)組pending只保存單個(gè)回調(diào)的形式,改為同時(shí)保存肯定和否定的兩種回調(diào)的形式。而且,在then中定義了默認(rèn)的肯定和否定回調(diào),使得then方法滿足了promise的2個(gè)可選參數(shù)的要求。
你也許注意到defer中還是只有一個(gè)resolve方法,而沒(méi)有類似jQuery的reject。那么,錯(cuò)誤處理要如何觸發(fā)呢?請(qǐng)看這個(gè)例子:
var defer1 = defer(), promise1 = defer1.promise; promise1.then(function(value){ console.log("1: value = ", value); return reject("error happens"); }).then(function(value){ console.log("2: value = ", value); }).then(null, function(reason){ console.log("3: reason = ", reason); }); defer1.resolve(10); // Result: // 1: value = 10 // 3: reason = error happens
可以看出,每一個(gè)傳遞給then方法的返回值是很重要的,它將決定下一個(gè)then方法的調(diào)用結(jié)果。而如果像上面這樣返回工具函數(shù)reject生成的對(duì)象,就會(huì)觸發(fā)錯(cuò)誤處理。
融入異步終于到了最后的design/q7.js。直到前面的q6,還存在一個(gè)問(wèn)題,就是then方法運(yùn)行的時(shí)候,可能是同步的,也可能是異步的,這取決于傳遞給then的函數(shù)(例如直接返回一個(gè)值,就是同步,返回一個(gè)其他的promise,就可以是異步)。這種不確定性可能帶來(lái)潛在的問(wèn)題。因此,Q的后面這一步,是確保將所有then轉(zhuǎn)變?yōu)楫惒健?/p>
design/q7.js定義了另一個(gè)工具函數(shù)enqueue:
var enqueue = function (callback) { //process.nextTick(callback); // NodeJS setTimeout(callback, 1); // Na?ve browser solution };
顯然,這個(gè)工具函數(shù)會(huì)將任意函數(shù)推遲到下一個(gè)事件隊(duì)列運(yùn)行。
design/q7.js其他的修改點(diǎn)是(只顯示修改部分):
var ref = function (value) { // ... return { then: function (callback) { var result = defer(); // XXX enqueue(function () { result.resolve(callback(value)); }); return result.promise; } }; }; var reject = function (reason) { return { then: function (callback, errback) { var result = defer(); // XXX enqueue(function () { result.resolve(errback(reason)); }); return result.promise; } }; }; var defer = function () { var pending = [], value; return { resolve: function (_value) { // ... enqueue(function () { value.then.apply(value, pending[i]); }); // ... }, promise: { then: function (_callback, _errback) { // ... enqueue(function () { value.then(callback, errback); }); // ... } } }; };
即把原來(lái)的value.then的部分,都轉(zhuǎn)變?yōu)楫惒健?/p>
到此,Q提供的Promise設(shè)計(jì)原理q0~q7,全部結(jié)束。
結(jié)語(yǔ)即便本文已經(jīng)是這么長(zhǎng)的篇幅,但所講述的也只到基礎(chǔ)的Promise。大部分Promise庫(kù)會(huì)有更多的API來(lái)應(yīng)對(duì)更多和Promise有關(guān)的需求,例如all()、spread(),不過(guò),讀到這里,你已經(jīng)了解了實(shí)現(xiàn)Promise的核心理念,這一定對(duì)你今后應(yīng)用Promise有所幫助。
在我看來(lái),Promise是精巧的設(shè)計(jì),我花了相當(dāng)一些時(shí)間才差不多理解它。Q作為一個(gè)典型Promise庫(kù),在思路上走得很明確??梢愿惺艿?,再?gòu)?fù)雜的庫(kù)也是先從基本的要點(diǎn)開(kāi)始的,如果我們自己要做類似的事,也應(yīng)該保持這樣的心態(tài)一點(diǎn)一點(diǎn)進(jìn)步。
(重新編輯自我的博客,原文地址:http://acgtofe.com/posts/2015...)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/85560.html
摘要:當(dāng)引擎開(kāi)始執(zhí)行一個(gè)函數(shù)比如回調(diào)函數(shù)時(shí),它就會(huì)把這個(gè)函數(shù)執(zhí)行完,也就是說(shuō)只有執(zhí)行完這段代碼才會(huì)繼續(xù)執(zhí)行后面的代碼。當(dāng)條件允許時(shí),回調(diào)函數(shù)就會(huì)被運(yùn)行?,F(xiàn)在,返回去執(zhí)行注冊(cè)的那個(gè)回調(diào)函數(shù)。 原文地址:http://blog.getify.com/promis... 在微博上看到有人分享LabJS作者寫(xiě)的關(guān)于Promise的博客,看了下覺(jué)得寫(xiě)得很好,分五個(gè)部分講解了Promise的來(lái)龍去脈。從...
摘要:有一個(gè)和相關(guān)的更大的問(wèn)題。最后,請(qǐng)負(fù)有責(zé)任感并且使用安全的擴(kuò)展。深入理解五部曲異步問(wèn)題深入理解五部曲轉(zhuǎn)換問(wèn)題深入理解五部曲可靠性問(wèn)題深入理解五部曲擴(kuò)展性問(wèn)題深入理解五部曲樂(lè)高問(wèn)題最后,安利下我的個(gè)人博客,歡迎訪問(wèn) 原文地址:http://blog.getify.com/promis... 現(xiàn)在,我希望你已經(jīng)看過(guò)深入理解Promise的前三篇文章了。并且假設(shè)你已經(jīng)完全理解Promises...
摘要:簡(jiǎn)單的說(shuō),即將到來(lái)的標(biāo)準(zhǔn)指出是一個(gè),所以作為一個(gè),必須可以被子類化。保護(hù)還是子類化這是個(gè)問(wèn)題我真的希望我能創(chuàng)建一個(gè)忠實(shí)的給及以下。 原文地址:http://blog.getify.com/promis... 如果你需要趕上我們關(guān)于Promise的進(jìn)度,可以看看這個(gè)系列前兩篇文章深入理解Promise五部曲--1.異步問(wèn)題和深入理解Promise五部曲--2.控制權(quán)轉(zhuǎn)移問(wèn)題。 Promi...
摘要:標(biāo)準(zhǔn)已于年月份正式定稿了,并廣泛支持最新的特性異步函數(shù)。為了領(lǐng)會(huì),我們需要回到普通回調(diào)函數(shù)中進(jìn)一步學(xué)習(xí)。從此編寫(xiě)回調(diào)函數(shù)不再那么痛苦。回調(diào)是一個(gè)函數(shù),可以將結(jié)果傳遞給函數(shù)并在該函數(shù)內(nèi)進(jìn)行調(diào)用,以便作為事件的響應(yīng)。 ES2017標(biāo)準(zhǔn)已于2017年6月份正式定稿了,并廣泛支持最新的特性:異步函數(shù)。如果你曾經(jīng)被異步 JavaScript 的邏輯困擾,這么新函數(shù)正是為你設(shè)計(jì)的。 異步函數(shù)或多或...
摘要:忍者級(jí)別的函數(shù)操作對(duì)于什么是匿名函數(shù),這里就不做過(guò)多介紹了。我們需要知道的是,對(duì)于而言,匿名函數(shù)是一個(gè)很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個(gè)供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果...
閱讀 1720·2023-04-25 18:19
閱讀 2148·2021-10-26 09:48
閱讀 1207·2021-10-09 09:44
閱讀 1815·2021-09-09 11:35
閱讀 3096·2019-08-30 15:54
閱讀 2130·2019-08-30 11:26
閱讀 2345·2019-08-29 17:06
閱讀 966·2019-08-29 16:38