摘要:換句話說(shuō),我們很好的對(duì)代碼的功能關(guān)注點(diǎn)進(jìn)行了分離通過(guò)將使用消費(fèi)值得地方函數(shù)中的邏輯和通過(guò)異步流程來(lái)獲取值迭代器的方法進(jìn)行了有效的分離。但是現(xiàn)在我們通過(guò)來(lái)管理代碼的異步流程部分,我們解決了回調(diào)函數(shù)所帶來(lái)的反轉(zhuǎn)控制等問(wèn)題。
ES6 Generators:完整系列本文翻譯自 Going Async With ES6 Generators
由于個(gè)人能力知識(shí)有限,翻譯過(guò)程中難免有紕漏和錯(cuò)誤,還望指正Issue
The Basics Of ES6 Generators
Diving Deeper With ES6 Generators
Going Async With ES6 Generators
Getting Concurrent With ES6 Generators
到目前為止,你已經(jīng)對(duì)ES6 generators有了初步了解并且能夠方便的使用它,是時(shí)候準(zhǔn)備將其運(yùn)用到真實(shí)項(xiàng)目中提高現(xiàn)有代碼質(zhì)量。
Generator函數(shù)的強(qiáng)大在于允許你通過(guò)一些實(shí)現(xiàn)細(xì)節(jié)來(lái)將異步過(guò)程隱藏起來(lái),依然使代碼保持一個(gè)單線程、同步語(yǔ)法的代碼風(fēng)格。這樣的語(yǔ)法使得我們能夠很自然的方式表達(dá)我們程序的步驟/語(yǔ)句流程,而不需要同時(shí)去操作一些異步的語(yǔ)法格式。
換句話說(shuō),我們很好的對(duì)代碼的功能/關(guān)注點(diǎn)進(jìn)行了分離:通過(guò)將使用(消費(fèi))值得地方(generator函數(shù)中的邏輯)和通過(guò)異步流程來(lái)獲取值(generator迭代器的next()方法)進(jìn)行了有效的分離。
結(jié)果就是?不僅我們的代碼具有強(qiáng)大的異步能力, 同時(shí)又保持了可讀性和可維護(hù)性的同步語(yǔ)法的代碼風(fēng)格。
那么我們?cè)趺磳?shí)現(xiàn)這些功能呢?
最簡(jiǎn)單的異步實(shí)現(xiàn)最簡(jiǎn)單的情況,generator函數(shù)不需要額外的代碼來(lái)處理異步功能,因?yàn)槟愕某绦蛞膊恍枰@樣做。
例如,讓我們假象你已經(jīng)寫(xiě)下了如下代碼:
function makeAjaxCall(url,cb) { // do some ajax fun // call `cb(result)` when complete } makeAjaxCall( "http://some.url.1", function(result1){ var data = JSON.parse( result1 ); makeAjaxCall( "http://some.url.2/?id=" + data.id, function(result2){ var resp = JSON.parse( result2 ); console.log( "The value you asked for: " + resp.value ); }); } );
通過(guò)generator函數(shù)(不帶任何其他裝飾)來(lái)實(shí)現(xiàn)和上面代碼相同的功能,實(shí)現(xiàn)代碼如下:
function request(url) { // this is where we"re hiding the asynchronicity, // away from the main code of our generator // `it.next(..)` is the generator"s iterator-resume // call makeAjaxCall( url, function(response){ it.next( response ); } ); // Note: nothing returned here! } function *main() { var result1 = yield request( "http://some.url.1" ); var data = JSON.parse( result1 ); var result2 = yield request( "http://some.url.2?id=" + data.id ); var resp = JSON.parse( result2 ); console.log( "The value you asked for: " + resp.value ); } var it = main(); it.next(); // get it all started
讓我來(lái)解釋下上面代碼是如何工作的。
request(..)幫助函數(shù)主要對(duì)普通的makeAjaxCall(..)實(shí)用函數(shù)進(jìn)行包裝,保證在在其回調(diào)函數(shù)中調(diào)用generator迭代器的next(..)方法。
在調(diào)用request(..)的過(guò)程中,你可能已經(jīng)發(fā)現(xiàn)函數(shù)并沒(méi)有顯式的返回值(換句話說(shuō),其返回undefined)。這沒(méi)有什么大不了的,但是與本文后面的方法相比,返回值就顯得比較重要了。這兒我們生效的yield undefined。
當(dāng)我們代碼執(zhí)行到yield..時(shí)(yield表達(dá)式返回undefined值),我們僅僅在這一點(diǎn)暫停了我們的generator函數(shù)而沒(méi)有做其他任何事。等待著it.next(..)方法的執(zhí)行來(lái)重新啟動(dòng)該generator函數(shù),而it.next()方法是在Ajax獲取數(shù)據(jù)結(jié)束后的回調(diào)函數(shù)(推入異步隊(duì)列等待執(zhí)行)中執(zhí)行的。
我們對(duì)yield..表達(dá)式的結(jié)果做了什么呢?我們將其結(jié)果賦值給了變量result1。那么我們是怎么將Ajax請(qǐng)求結(jié)果放到該yield..表達(dá)式的返回值中的呢?
因?yàn)楫?dāng)我們?cè)贏jax的回調(diào)函數(shù)中調(diào)用it.next(..)方法的時(shí)候,我們將Ajax的返回值作為參數(shù)傳遞給next(..)方法,這意味著該Ajax返回值傳遞到了generator函數(shù)內(nèi)部,當(dāng)前函數(shù)內(nèi)部暫停的位置,也就是result1 = yield..語(yǔ)句中部。
上面的代碼真的很酷并且強(qiáng)大。本質(zhì)上,result1 = yield request(..)的作用是用來(lái)請(qǐng)求值,但是請(qǐng)求的過(guò)程幾乎完全對(duì)我們不可見(jiàn)- -或者至少在此處我們不用怎么擔(dān)心它 - - 因?yàn)榈讓拥膶?shí)現(xiàn)使得該步驟成為了異步操作。generator函數(shù)通過(guò)通過(guò)在yield表達(dá)式中隱藏的暫停功能以及將重新啟動(dòng)generator函數(shù)的功能分離到另外一個(gè)函數(shù)中,來(lái)實(shí)現(xiàn)了異步操作。因此在主要代碼中我們通過(guò)一個(gè)同步的代碼風(fēng)格來(lái)請(qǐng)求值。
第二句result2 = yield result()(譯者注:作者的筆誤,應(yīng)該是result2 = yield request(..))代碼,和上面的代碼工作原理幾乎無(wú)異:通過(guò)明顯的暫停和重新啟動(dòng)機(jī)制來(lái)獲取到我們請(qǐng)求的數(shù)據(jù),而在generator函數(shù)內(nèi)部我們不用再為一些異步代碼細(xì)節(jié)為煩惱。
當(dāng)然,yield的出現(xiàn),也就微妙的暗示一些神奇(??!異步)的事情可能在此處發(fā)生。和嵌套回調(diào)函數(shù)帶來(lái)的回調(diào)地獄相比,yield在語(yǔ)法層面上優(yōu)于回調(diào)函數(shù)(甚至在API上優(yōu)于promise的鏈?zhǔn)秸{(diào)用)。
需要注意上面我說(shuō)的是“可能”。generator函數(shù)完成上面的工作,這本身就是一件非常強(qiáng)大的事情。上面的程序始終發(fā)送一個(gè)異步的Ajax請(qǐng)求,假如不發(fā)送異步Ajax請(qǐng)求呢?倘若我們改變我們的程序來(lái)從緩存中獲取到先前(或者預(yù)先請(qǐng)求)Ajax請(qǐng)求的結(jié)果?或者從我們的URL路由中獲取數(shù)據(jù)來(lái)立刻fulfillAjax請(qǐng)求,而不用真正的向后端請(qǐng)求數(shù)據(jù)。
我們可以改變我們的request(..)函數(shù)來(lái)滿足上面的需求,如下:
var cache = {}; function request(url) { if (cache[url]) { // "defer" cached response long enough for current // execution thread to complete setTimeout( function(){ it.next( cache[url] ); }, 0 ); } else { makeAjaxCall( url, function(resp){ cache[url] = resp; it.next( resp ); } ); } }
注意:在上面的代碼中我們使用了一個(gè)細(xì)微的技巧setTimeout(..0),當(dāng)從緩存中獲取結(jié)果時(shí)來(lái)延遲代碼的執(zhí)行。如果我們不延遲而是立即執(zhí)行it.next(..)方法,這將會(huì)導(dǎo)致錯(cuò)誤的發(fā)生,因?yàn)椋ㄟ@就是技巧所在)此時(shí)generator函數(shù)還沒(méi)有停止執(zhí)行。首先我們執(zhí)行request(..)函數(shù),然后通過(guò)yield來(lái)暫停generator函數(shù)。因此不能夠在request(..)函數(shù)中立即調(diào)用it.next(..)方法,因?yàn)樵诖藭r(shí),generator函數(shù)依然在運(yùn)行(yield 還沒(méi)有被調(diào)用)。但是我們可以在當(dāng)前線程運(yùn)行結(jié)束后,立即執(zhí)行it.next(..)。這就是setTimeout(..0)將要完成的工作。在文章后面我們將看到一個(gè)更加完美的解答。
現(xiàn)在,我們generator函數(shù)內(nèi)部主要代碼依然如下:
var result1 = yield request( "http://some.url.1" ); var data = JSON.parse( result1 ); ..
看到?jīng)]?。?/strong>當(dāng)我們代碼從沒(méi)有緩存到上面有緩存的版本,我們generator函數(shù)內(nèi)部邏輯(我們的控制流程)竟然沒(méi)有變化。
*main()函數(shù)內(nèi)部代碼依然是請(qǐng)求數(shù)據(jù),暫停generator函數(shù)的執(zhí)行來(lái)等待數(shù)據(jù)的返回,數(shù)據(jù)傳回后繼續(xù)執(zhí)行。在我們當(dāng)前場(chǎng)景中,這個(gè)暫停可能相對(duì)比較長(zhǎng)(真實(shí)的向服務(wù)器發(fā)送請(qǐng)求,這可能會(huì)耗時(shí)300~800ms)或者幾乎立即執(zhí)行(使用setTimeout(..0)手段延遲執(zhí)行)。但是我們*main函數(shù)中的控制流程不用關(guān)心數(shù)據(jù)從何而來(lái)。
這就是從實(shí)現(xiàn)細(xì)節(jié)中將異步流程分離出來(lái)的強(qiáng)大力量。
更好的異步編程利用上面提及的方法(回調(diào)函數(shù)),generators函數(shù)能夠完成一些簡(jiǎn)單的異步工作。但是卻相當(dāng)局限,因此我們需要一個(gè)更加強(qiáng)大的異步機(jī)制來(lái)與我們的generator函數(shù)匹配結(jié)合。完成一些更加繁重的異步流程。什么異步機(jī)制呢?Promises。
如果你依然對(duì)ES6 Promises感到困惑,我寫(xiě)過(guò)關(guān)于Promise的系列文章。去閱讀一下。我會(huì)等待你回來(lái),<滴答,滴答>。老掉牙的異步笑話了。
先前的Ajax代碼例子依然存在反轉(zhuǎn)控制的問(wèn)題(啊,回調(diào)地獄)正如文章最初的嵌套回調(diào)函數(shù)例子一樣。到目前為止,我們應(yīng)該已經(jīng)明顯察覺(jué)到了上面的例子存在一些待完善的地方:
到目前為止沒(méi)有明確的錯(cuò)誤處理機(jī)制,正如我們上一篇學(xué)習(xí)的文章,在發(fā)送Ajax請(qǐng)求的過(guò)程中我們可能檢測(cè)到錯(cuò)誤(在某處),通過(guò)it.throw(..)方法將錯(cuò)誤傳遞會(huì)generator函數(shù),然后在generator函數(shù)內(nèi)部通過(guò)try..catch模塊來(lái)處理該錯(cuò)誤。但是,我們?cè)凇昂竺妗睂⒁謩?dòng)處理更多工作(更多的代碼來(lái)處理我們的generator迭代器),如果在我們的程序中多次使用generators函數(shù),這些錯(cuò)誤處理代碼很難被復(fù)用。
如果makeAjaxCall(..)工具函數(shù)不受我們控制,碰巧它多次調(diào)用了回調(diào)函數(shù),或者同時(shí)將成功值或者錯(cuò)誤返回到generator函數(shù)中,等等。我們的generator函數(shù)就將變得極難控制(未捕獲的錯(cuò)誤,意外的返回值等)。處理、阻止上述問(wèn)題的發(fā)生很多都是一些重復(fù)的工作,同時(shí)也都不是輕輕松松能夠完成的。
很多時(shí)候我們需要同時(shí)并行處理多個(gè)任務(wù)(例如兩個(gè)并行的Ajax請(qǐng)求)。由于generator函數(shù)中的yield表達(dá)式執(zhí)行后都會(huì)暫停函數(shù)的執(zhí)行,不能夠同時(shí)運(yùn)行兩個(gè)或多個(gè)yield表達(dá)式,也就是說(shuō)yield表達(dá)式只能按順序一個(gè)接一個(gè)的運(yùn)行。因此在沒(méi)有大量手寫(xiě)代碼的前提下,一個(gè)yield表達(dá)式中同時(shí)執(zhí)行多個(gè)任務(wù)依然不太明朗。
正如你所見(jiàn),上面的所有問(wèn)題都可以被解決,但是又有誰(shuí)愿意每次重復(fù)手寫(xiě)這些代碼呢?我們需要一種更加強(qiáng)大的模式,該模式是可信賴(lài)且高度復(fù)用的,并且能夠很好的解決generator函數(shù)處理異步流程問(wèn)題。
什么模式?yield 表達(dá)式內(nèi)部是promise,當(dāng)這些promise被fulfill后重新啟動(dòng)generator函數(shù)。
回憶上面代碼,我們使用yield request(..),但是request(..)工具函數(shù)并沒(méi)有返回任何值,那么它僅僅yield undefined嗎?
讓我們稍微調(diào)整下上面的代碼。我們把request(..)函數(shù)改為以promise為基礎(chǔ)的函數(shù),因此該函數(shù)返回一個(gè)promise,現(xiàn)在我們通過(guò)yield表達(dá)式返回了一個(gè)真實(shí)的promise(而不是undefined)。
function request(url) { // Note: returning a promise now! return new Promise( function(resolve,reject){ makeAjaxCall( url, resolve ); } ); }
request(..)函數(shù)通過(guò)構(gòu)建一個(gè)promise來(lái)監(jiān)聽(tīng)Ajax的完成并且resolve返回值,并且返回該promise,因此promise也能夠被yield傳遞到generator函數(shù)外部,接下來(lái)呢?
我們需要一個(gè)工具函數(shù)來(lái)控制generator函數(shù)的迭代器,該工具函數(shù)接收yield表達(dá)式傳遞出來(lái)的promise,然后在promie 狀態(tài)轉(zhuǎn)為fulfill或者reject時(shí),通過(guò)迭代器的next(..)方法重新啟動(dòng)generator函數(shù)?,F(xiàn)在我為這個(gè)工具函數(shù)取名runGenerator(..):
// run (async) a generator to completion // Note: simplified approach: no error handling here function runGenerator(g) { var it = g(), ret; // asynchronously iterate over generator (function iterate(val){ ret = it.next( val ); if (!ret.done) { // poor man"s "is it a promise?" test if ("then" in ret.value) { // wait on the promise ret.value.then( iterate ); } // immediate value: just send right back in else { // avoid synchronous recursion setTimeout( function(){ iterate( ret.value ); }, 0 ); } } })(); }
需要注意的關(guān)鍵點(diǎn):
我們自動(dòng)的初始化了generator函數(shù)(創(chuàng)建了it迭代器),然后我們異步運(yùn)行it來(lái)完成generator函數(shù)的執(zhí)行(done: true)。
我們尋找被yield表達(dá)式傳遞出來(lái)的promise(啊,也就是執(zhí)行it.next(..)方法后返回的對(duì)象中的value字段)。如此,我們通過(guò)在promise的then(..)方法中注冊(cè)函數(shù)來(lái)監(jiān)聽(tīng)器完成。
如果一個(gè)非promise值被傳遞出來(lái),我們僅僅將該值原樣返回到generator函數(shù)內(nèi)部,因此看上去立即重新啟動(dòng)了generator函數(shù)。
現(xiàn)在我們?cè)趺词褂盟兀?/p>
runGenerator( function *main(){ var result1 = yield request( "http://some.url.1" ); var data = JSON.parse( result1 ); var result2 = yield request( "http://some.url.2?id=" + data.id ); var resp = JSON.parse( result2 ); console.log( "The value you asked for: " + resp.value ); } );
騙人!等等...上面代碼和更早的代碼幾乎完全一樣?哈哈,generator函數(shù)再次向我們炫耀了它的強(qiáng)大之處。實(shí)際上我們創(chuàng)建了promise,通過(guò)yield將其傳遞出去,然后重新啟動(dòng)generator函數(shù),直到函數(shù)執(zhí)行完成- - 所有被""隱藏""的實(shí)現(xiàn)細(xì)節(jié)!實(shí)際上并沒(méi)有隱藏起來(lái),只是和我們消費(fèi)該異步流程的代碼(generator中的控制流程)隔離開(kāi)來(lái)了。
通過(guò)等待yield出去的promise的完成,然后將fulfill的值通過(guò)it.next(..)方法傳遞回函數(shù)中,result1 = yield request(..)表達(dá)式就回獲取到正如先前一樣的請(qǐng)求值。
但是現(xiàn)在我們通過(guò)promises來(lái)管理generator代碼的異步流程部分,我們解決了回調(diào)函數(shù)所帶來(lái)的反轉(zhuǎn)控制等問(wèn)題。通過(guò)generator+promises的模式我們“免費(fèi)”解決上述所遇到的問(wèn)題:
現(xiàn)在我們用易用的內(nèi)部錯(cuò)誤處理機(jī)制。在runGenerator(..)函數(shù)中我們并沒(méi)有提及,但是監(jiān)聽(tīng)promise的錯(cuò)誤并非難事,我們只需通過(guò)it.throw(..)方法將promise捕獲的錯(cuò)誤拋進(jìn)generator函數(shù)內(nèi)部,在函數(shù)內(nèi)部通過(guò)try...catch模塊進(jìn)行錯(cuò)誤捕獲及處理。
promise給我們提供了可控性/可依賴(lài)性。不用擔(dān)心,也不用疑惑。
Promises擁有一些強(qiáng)大的抽象工具方法,利用這些方法可以自動(dòng)處理一些復(fù)雜的“并行”任務(wù)等。
例如,yield Prmise.all([ .. ])可以接受一個(gè)promise數(shù)組然后“并行”執(zhí)行這些任務(wù),然后yield出去一個(gè)多帶帶的promise(給generator函數(shù)處理),該promise將會(huì)等待所有并行的promise都完成后才被完成,你可以通過(guò)yield表達(dá)式的返回?cái)?shù)組(當(dāng)promise完成后)來(lái)獲取到所有并行promise的結(jié)果。數(shù)組中的結(jié)果和并行promises任務(wù)一一對(duì)應(yīng)(因此其完全忽略promise完成的順序)。
首先,讓我們研究下錯(cuò)誤處理:
// assume: `makeAjaxCall(..)` now expects an "error-first style" callback (omitted for brevity) // assume: `runGenerator(..)` now also handles error handling (omitted for brevity) function request(url) { return new Promise( function(resolve,reject){ // pass an error-first style callback makeAjaxCall( url, function(err,text){ if (err) reject( err ); else resolve( text ); } ); } ); } runGenerator( function *main(){ try { var result1 = yield request( "http://some.url.1" ); } catch (err) { console.log( "Error: " + err ); return; } var data = JSON.parse( result1 ); try { var result2 = yield request( "http://some.url.2?id=" + data.id ); } catch (err) { console.log( "Error: " + err ); return; } var resp = JSON.parse( result2 ); console.log( "The value you asked for: " + resp.value ); } );
當(dāng)再URL 請(qǐng)求發(fā)出后一個(gè)promise被reject后(或者其他的錯(cuò)誤或異常),這個(gè)promise的reject值將會(huì)映射到一個(gè)generator函數(shù)錯(cuò)誤(通過(guò)runGenerator(..)內(nèi)部隱式的it.throw(..)來(lái)傳遞錯(cuò)誤),該錯(cuò)誤將會(huì)被try..catch模塊捕獲。
現(xiàn)在,讓我們看一個(gè)通過(guò)promises來(lái)管理更加錯(cuò)綜復(fù)雜的異步流程的事例:
function request(url) { return new Promise( function(resolve,reject){ makeAjaxCall( url, resolve ); } ) // do some post-processing on the returned text .then( function(text){ // did we just get a (redirect) URL back? if (/^https?://.+/.test( text )) { // make another sub-request to the new URL return request( text ); } // otherwise, assume text is what we expected to get back else { return text; } } ); } runGenerator( function *main(){ var search_terms = yield Promise.all( [ request( "http://some.url.1" ), request( "http://some.url.2" ), request( "http://some.url.3" ) ] ); var search_results = yield request( "http://some.url.4?search=" + search_terms.join( "+" ) ); var resp = JSON.parse( search_results ); console.log( "Search results: " + resp.value ); } );
Promise.all([ .. ])會(huì)構(gòu)建一個(gè)新的promise來(lái)等待其內(nèi)部的三個(gè)并行promise的完成,該新的promise將會(huì)被yield表達(dá)式傳遞到外部給runGenerator(..)工具函數(shù)中,runGenerator()函數(shù)監(jiān)聽(tīng)該新生成的promise的完成,以便重新啟動(dòng)generator函數(shù)。并行的promise的返回值可能會(huì)成為另外一個(gè)URL的組成部分,然后通過(guò)yield表達(dá)式將另外一個(gè)promise傳遞到外部。關(guān)于更多的promise鏈?zhǔn)秸{(diào)用,參見(jiàn)文章
promise可以處理任何復(fù)雜的異步過(guò)程,你可以通過(guò)generator函數(shù)yield出去promises(或者promise返回promise)來(lái)獲取到同步代碼的語(yǔ)法形式。(對(duì)于promise或者generator兩個(gè)ES6的新特性,他們的結(jié)合或許是最好的模式)
runGenerator(..): 實(shí)用函數(shù)庫(kù)在上面我們已經(jīng)定義了runGenerator(..)工具函數(shù)來(lái)順利幫助我們充分發(fā)揮generator+promise模式的卓越能力。我們甚至省略了(為了簡(jiǎn)略起見(jiàn))該工具函數(shù)的完整實(shí)現(xiàn),在錯(cuò)誤處理方面依然有些細(xì)微細(xì)節(jié)我們需要處理。
但是,你不愿意實(shí)現(xiàn)一個(gè)你自己的runGenerator(..)是嗎?
我不這么認(rèn)為。
許多promise/async庫(kù)都提供了上述工具函數(shù)。在此我不會(huì)一一論述,但是你一個(gè)查閱Q.spawn(..)和co(..)庫(kù),等等。
但是我會(huì)簡(jiǎn)要的闡述我自己的庫(kù)asynquence中的runner(..)插件,相對(duì)于其他庫(kù),我想提供一些獨(dú)一無(wú)二的特性。如果對(duì)此感興趣并想學(xué)習(xí)更多關(guān)于asynquence的知識(shí)而不是淺嘗輒止,可以看看以前的兩篇文章深入asynquence
首先,asynquence提供了自動(dòng)處理上面代碼片段中的”error-first-style“回調(diào)函數(shù)的工具函數(shù):
function request(url) { return ASQ( function(done){ // pass an error-first style callback makeAjaxCall( url, done.errfcb ); } ); }
是不是看起來(lái)更加好看,不是嗎???
接下來(lái),asynquence提供了runner(..)插件來(lái)在異步序列(異步流程)中執(zhí)行g(shù)enerator函數(shù),因此你可以在runner前面的步驟傳遞信息到generator函數(shù)內(nèi),同時(shí)generator函數(shù)也可以傳遞消息出去到下一個(gè)步驟中,同時(shí)如你所愿,所有的錯(cuò)誤都自動(dòng)冒泡被最后的or所捕獲。
// first call `getSomeValues()` which produces a sequence/promise, // then chain off that sequence for more async steps getSomeValues() // now use a generator to process the retrieved values .runner( function*(token){ // token.messages will be prefilled with any messages // from the previous step var value1 = token.messages[0]; var value2 = token.messages[1]; var value3 = token.messages[2]; // make all 3 Ajax requests in parallel, wait for // all of them to finish (in whatever order) // Note: `ASQ().all(..)` is like `Promise.all(..)` var msgs = yield ASQ().all( request( "http://some.url.1?v=" + value1 ), request( "http://some.url.2?v=" + value2 ), request( "http://some.url.3?v=" + value3 ) ); // send this message onto the next step yield (msgs[0] + msgs[1] + msgs[2]); } ) // now, send the final result of previous generator // off to another request .seq( function(msg){ return request( "http://some.url.4?msg=" + msg ); } ) // now we"re finally all done! .val( function(result){ console.log( result ); // success, all done! } ) // or, we had some error! .or( function(err) { console.log( "Error: " + err ); } );
asyquence的runner(..)工具接受上一步序列傳遞下來(lái)的值(也有可能沒(méi)有值)來(lái)啟動(dòng)generator函數(shù),可以通過(guò)token.messages數(shù)組來(lái)獲取到傳入的值。
然后,和上面我們所描述的runGenerator(..)工具函數(shù)類(lèi)似,runner(..)也會(huì)監(jiān)聽(tīng)yield一個(gè)promise或者yield一個(gè)asynquence序列(在本例中,是指通過(guò)ASQ().all()方法生成的”并行”任務(wù)),然后等待promise或者asynquence序列的完成后重新啟動(dòng)generator函數(shù)。
當(dāng)generator函數(shù)執(zhí)行完成后,最后通過(guò)yield表達(dá)式傳遞的值將作為參數(shù)傳遞到下一個(gè)序列步驟中。
最后,如果在某個(gè)序列步驟中出現(xiàn)錯(cuò)誤,甚至在generator內(nèi)部,錯(cuò)誤都會(huì)冒泡到被注冊(cè)的or(..)方法中進(jìn)行錯(cuò)誤處理。
asynquence通過(guò)盡可能簡(jiǎn)單的方式來(lái)混合匹配promises和generator。你可以自由的在以promise為基礎(chǔ)的序列流程后面接generator控制流程,正如上面代碼。
ES7 async在ES7的時(shí)間軸上有一個(gè)提案,并且有極大可能被接受,該提案將在JavaScript中添加另外一個(gè)函數(shù)類(lèi)型:async函數(shù),該函數(shù)相當(dāng)于用類(lèi)似于runGenerator(..)(或者asynquence的runner(..))工具函數(shù)在generator函數(shù)外部包裝一下,來(lái)使得其自動(dòng)執(zhí)行。通過(guò)async函數(shù),你可以把promises傳遞到外部然后async函數(shù)在promises狀態(tài)變?yōu)閒ulfill時(shí)自動(dòng)重新啟動(dòng)直到函數(shù)執(zhí)行完成。(甚至不需要復(fù)雜的迭代器參與)
async函數(shù)大概形式如下:
async function main() { var result1 = await request( "http://some.url.1" ); var data = JSON.parse( result1 ); var result2 = await request( "http://some.url.2?id=" + data.id ); var resp = JSON.parse( result2 ); console.log( "The value you asked for: " + resp.value ); } main();
正如你所見(jiàn),async 函數(shù)可以想普通函數(shù)一樣被調(diào)用(如main()),而不需要包裝函數(shù)如runGenerator(..)或者ASQ().runner(..)的幫助。同時(shí),函數(shù)內(nèi)部不再使用yield,而是使用await(另外一個(gè)JavaScript關(guān)鍵字)關(guān)鍵字來(lái)告訴async 函數(shù)等待當(dāng)前promise得到返回值后繼續(xù)執(zhí)行。
基本上,async函數(shù)擁有通過(guò)一些包裝庫(kù)調(diào)用generator函數(shù)的大部分功能,同時(shí)關(guān)鍵是其被原生語(yǔ)法所支持。
是不是很酷???
同時(shí),像asynquence這樣的工具集使得我們能夠輕易的且充分利用generator函數(shù)完成異步工作。
總結(jié)簡(jiǎn)單地說(shuō):通過(guò)把promise和generator函數(shù)兩個(gè)世界組合起來(lái)成為generator + yield promise(s)模式,該模式具有強(qiáng)大的能力及同步語(yǔ)法形式的異步表達(dá)能力。通過(guò)一些簡(jiǎn)單包裝的工具(很多庫(kù)已經(jīng)提供了這些工具),我們可以讓generator函數(shù)自動(dòng)執(zhí)行完成,并且提供了健全和同步語(yǔ)法形式的錯(cuò)誤處理機(jī)制。
同時(shí)在ES7+的將來(lái),我們也許將迎來(lái)async function函數(shù),async 函數(shù)將不需要上面那些工具庫(kù)就能夠解決上面遇到的那些問(wèn)題(至少對(duì)于基礎(chǔ)問(wèn)題是可行的)!
JavaScript的異步處理機(jī)制的未來(lái)是光明的,而且會(huì)越來(lái)越光明!我要帶墨鏡了。(譯者注:這兒是作者幽默的說(shuō)法)
但是,我們并沒(méi)有在這兒就結(jié)束本系列文章,這兒還有最后一個(gè)方面我們想要研究:
倘若你想要將兩個(gè)或多個(gè)generator函數(shù)結(jié)合在一起,讓他們獨(dú)立平行的運(yùn)行,并且在它們執(zhí)行的過(guò)程中來(lái)來(lái)回回得傳遞信息?這一定會(huì)成為一個(gè)相當(dāng)強(qiáng)大的特性,難道不是嗎?這一模式被稱(chēng)作“CSP”(communicating sequential processes)。我們將在下面一篇文章中解鎖CSP的能力。敬請(qǐng)密切關(guān)注。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/87352.html
摘要:傳統(tǒng)的異步方法回調(diào)函數(shù)事件監(jiān)聽(tīng)發(fā)布訂閱之前寫(xiě)過(guò)一篇關(guān)于的文章,里邊寫(xiě)過(guò)關(guān)于異步的一些概念。內(nèi)部函數(shù)就是的回調(diào)函數(shù),函數(shù)首先把函數(shù)的指針指向函數(shù)的下一步方法,如果沒(méi)有,就把函數(shù)傳給函數(shù)屬性,否則直接退出。 Generator函數(shù)與異步編程 因?yàn)閖s是單線程語(yǔ)言,所以需要異步編程的存在,要不效率太低會(huì)卡死。 傳統(tǒng)的異步方法 回調(diào)函數(shù) 事件監(jiān)聽(tīng) 發(fā)布/訂閱 Promise 之前寫(xiě)過(guò)一篇關(guān)...
摘要:而在中是迭代器生成器,被創(chuàng)造性的拿來(lái)做異步流程控制了。當(dāng)執(zhí)行的時(shí)候,并不執(zhí)行函數(shù)體,而是返回一個(gè)迭代器。行代碼再看看文章開(kāi)頭的行代碼首先生成一個(gè)迭代器,然后執(zhí)行一遍,得到的是一個(gè)對(duì)象,里面再執(zhí)行。 廣告招人:阿里巴巴招前端,在這里你可以享受大公司的福利和技術(shù)體系,也有小團(tuán)隊(duì)的挑戰(zhàn)和成長(zhǎng)空間。聯(lián)系: qingguang.meiqg at alibaba-inc.com 首先請(qǐng)?jiān)徫业臉?biāo)題...
摘要:回調(diào)函數(shù)這是異步編程最基本的方法。對(duì)象對(duì)象是工作組提出的一種規(guī)范,目的是為異步編程提供統(tǒng)一接口。誕生后,出現(xiàn)了函數(shù),它將異步編程帶入了一個(gè)全新的階段。 更多詳情點(diǎn)擊http://blog.zhangbing.club/Ja... Javascript 語(yǔ)言的執(zhí)行環(huán)境是單線程的,如果沒(méi)有異步編程,根本沒(méi)法用,非卡死不可。 為了解決這個(gè)問(wèn)題,Javascript語(yǔ)言將任務(wù)的執(zhí)行模式分成兩種...
摘要:更好的異步編程上面的方法可以適用于那些比較簡(jiǎn)單的異步工作流程。小結(jié)的組合目前是最強(qiáng)大,也是最優(yōu)雅的異步流程管理編程方式。 訪問(wèn)原文地址 generators主要作用就是提供了一種,單線程的,很像同步方法的編程風(fēng)格,方便你把異步實(shí)現(xiàn)的那些細(xì)節(jié)藏在別處。這讓我們可以用一種很自然的方式書(shū)寫(xiě)我們代碼中的流程和狀態(tài)邏輯,不再需要去遵循那些奇怪的異步編程風(fēng)格。 換句話說(shuō),通過(guò)將我們generato...
摘要:關(guān)于協(xié)程和中的什么是協(xié)程進(jìn)程和線程眾所周知,進(jìn)程和線程都是一個(gè)時(shí)間段的描述,是工作時(shí)間段的描述,不過(guò)是顆粒大小不同,進(jìn)程是資源分配的最小單位,線程是調(diào)度的最小單位。子程序就是協(xié)程的一種特例。 關(guān)于協(xié)程和 ES6 中的 Generator 什么是協(xié)程? 進(jìn)程和線程 眾所周知,進(jìn)程和線程都是一個(gè)時(shí)間段的描述,是CPU工作時(shí)間段的描述,不過(guò)是顆粒大小不同,進(jìn)程是 CPU 資源分配的最小單位,...
閱讀 695·2021-11-24 09:39
閱讀 3534·2019-08-30 15:53
閱讀 2576·2019-08-30 15:44
閱讀 3299·2019-08-30 12:54
閱讀 2266·2019-08-29 12:23
閱讀 3356·2019-08-26 14:05
閱讀 2164·2019-08-26 13:36
閱讀 3496·2019-08-26 13:33