摘要:所以僅用于簡(jiǎn)化理解,快速入門(mén),依然需要閱讀有深入研究的文章來(lái)加深對(duì)各種異步流程控制的方法的掌握。
原文地址:http://zodiacg.net/2015/08/javascript-async-control-flow/
隨著ES6標(biāo)準(zhǔn)逐漸成熟,利用Promise和Generator解決回調(diào)地獄問(wèn)題的話題一直很熱門(mén)。但是對(duì)解決流程控制/回調(diào)地獄問(wèn)題的各種工具認(rèn)識(shí)仍然比較麻煩。最近兩天看了很多文章,想出幾個(gè)場(chǎng)景把各種異步流程方式類(lèi)比一下,希望能有助于理解他們的實(shí)現(xiàn)。
需要說(shuō)明的是類(lèi)比只能反映被類(lèi)比的事物的一個(gè)方面,必然有其反映不到的部分,不能完全以類(lèi)比來(lái)理解各種異步控制的本質(zhì)。所以僅用于簡(jiǎn)化理解,快速入門(mén),依然需要閱讀有深入研究的文章來(lái)加深對(duì)各種異步流程控制的方法的掌握。
文章中沒(méi)有嚴(yán)格使用Node.js核心模塊的函數(shù)名,而是偽造的函數(shù)名來(lái)方便在各種方式下保持一致性和簡(jiǎn)化書(shū)寫(xiě)。
同步從最簡(jiǎn)單的同步開(kāi)始。以一段經(jīng)典的代碼為例吧,傳入文件名,從文件中讀取內(nèi)容并按JSON格式解析,把其中的一部分內(nèi)容發(fā)還給用戶。
function foo(filename){ var file = readFile(filename); var json = parseJSON(file); return json.someContent; } var resultA=foo("first.json"); var resultB=foo("second.json");
其中readFile和parseJSON顯而易見(jiàn)是同步、阻塞的函數(shù)。
我們構(gòu)造這樣一個(gè)場(chǎng)景,M(ain)代表著解釋引擎的主線程,坐在柜臺(tái)前等待用戶提交請(qǐng)求。
來(lái)了兩個(gè)用戶A和用戶B,用戶A排在前面。
用戶A: 你好,我要讀文件first.json。
M:好的你等一下我去拿。
【M離開(kāi)了柜臺(tái)去拿文件】
M:好的我回來(lái)了,這是你要的first.json的內(nèi)容。然后呢?
用戶A:然后把內(nèi)容按JSON解析一下。
M:你等等我去解析一下。
【M離開(kāi)了柜臺(tái)去解析文件內(nèi)容】
M:好了解析完了,然后呢?
用戶A:我要里面的someContent部分。
M:給你。再見(jiàn)。用戶B:你好,我要讀文件second.json。
…………
可以看到同步阻塞顧名思義,操作一步一步進(jìn)行,遇到IO操作每一步都要等待,用戶A不處理完,用戶B也進(jìn)不來(lái)。
經(jīng)典回調(diào)方式仍然是這個(gè)需求,這次的代碼就比較像Node.js里的常見(jiàn)形式了,此處為了簡(jiǎn)單忽略了err。由于回調(diào)套回調(diào),縮進(jìn)如同金字塔,被稱(chēng)作回調(diào)地獄。當(dāng)然回調(diào)地獄遠(yuǎn)不是一個(gè)縮進(jìn)金字塔那么簡(jiǎn)單。
function foo(filename, cb){ readFile(filename,function(file){ parseJSON(file,function(json){ cb(json.someContent); }); }); } foo("first.json",cbA); foo("second.json",cbB);
依然是來(lái)了兩個(gè)用戶A和用戶B
用戶A:你好,我要讀文件first.json,按JSON解析后把里面的someContent寄往cbA發(fā)回給我。
M:好的。
【M開(kāi)始寫(xiě)信,信封上寫(xiě)上文件讀取處readFile,拿出一張信紙寫(xiě)到:“讀取first.json,然后內(nèi)容放進(jìn)后附信封中寄出”?!?
【M又拿出一個(gè)信封,寫(xiě)上JSON解析處parseJSON,信紙上寫(xiě)到“把給你的file按JSON解析,然后把里面的someContent放到所附信封里寄出”。】
【M又又拿出一個(gè)信封,寫(xiě)上cbA,然后把這個(gè)信封放進(jìn)了剛才的信封里,又把剛才的信封塞進(jìn)了第一個(gè)信封里?!?
【M把鼓鼓囊囊的信封扔到郵箱里就不管了】
M:下一位!
用戶B:你好……
寫(xiě)幾封信的時(shí)間比自己跑出去取文件要快的多。異步操作帶來(lái)的處理速度提升是顯而易見(jiàn)的。
但是為了保證業(yè)務(wù)流程的銜接,信里面就包含了后續(xù)一切需要進(jìn)行的操作,層層包裹。第一封信寄出,M就既無(wú)從得知信走到了何處,也無(wú)法控制readFile和parseJSON是不是如自己所想寄出了給他的信封,有沒(méi)有私自復(fù)印了多寄了一兩封。
這才是回調(diào)地獄真正危險(xiǎn)的地方,缺乏控制。
Promise引入Promise之后,很多人就以為能解決回調(diào)地獄了,其實(shí)不然。在某些場(chǎng)景下只是讓縮進(jìn)好看了一點(diǎn)而已。有些場(chǎng)景下縮進(jìn)也沒(méi)法好看,需要書(shū)寫(xiě)的回調(diào)不僅不會(huì)減少還會(huì)增多。
function foo(filename,cb){ readFile(filename) .then(parseJSON(file)) .then(function(json){ cb(json.someContent)}); } foo("first.json",cbA); foo("second.json",cbB);
當(dāng)然這里面的readFile和parseJSON已經(jīng)是Promise化了的。
依然是來(lái)了兩個(gè)用戶A和用戶B
用戶A:你好,我要讀文件first.json,按JSON解析后把里面的someContent寄往cbA發(fā)回給我。
M:好的。
【M叫來(lái)了一個(gè)辦事員小P】
M:小P你聽(tīng)好,先去找readFile讀取first.json,然后把內(nèi)容給parseJSON讓他解析一下,最后把解析的內(nèi)容里的someContent寄給cb,懂了嗎?
P:我辦事你放心!
【小P離開(kāi)了柜臺(tái)】
M:下一位!
用戶B:你好……
小P是一位M信得過(guò)的辦事員,M相信他能夠挨個(gè)去找該找的部門(mén),不偷工減料也不毛手毛腳。讓小P去辦事比寄一封信靠譜的多,M依然能很快回過(guò)頭來(lái)繼續(xù)應(yīng)付下一個(gè)用戶請(qǐng)求。
當(dāng)然實(shí)際場(chǎng)景中雖然寫(xiě)的時(shí)候.then一下子連起來(lái)寫(xiě)完,并不是真的一下子把內(nèi)容都交給同一位小P/同一個(gè)Promise。更像是一個(gè)Promise公司,每個(gè)操作進(jìn)行完后都由一位Promise公司的辦事員進(jìn)行下一步操作。
Promise物如其名,使用Promise重要的就是Promise的可信性,比如Promise的狀態(tài)不可逆,比如fulfill回調(diào)只會(huì)被調(diào)用一次。Promise并不是回避書(shū)寫(xiě)回調(diào),而是用一種更可靠的方式來(lái)書(shū)寫(xiě)回調(diào)。
Co(Generator)這里只談co不談Generator,是因?yàn)镚enerator并不是為解決異步流程控制而生的,而TJ大神用co把Generator和Thunk/Promise結(jié)合在一起提供了新的異步流程控制的方法。
var foo=co(function*(filename){ var file = yield readFile(filename); var json = yield parseJSON(file); return json.someContent; }); foo("first.json").then(cbA); foo("second.json").then(cbB);
咦?這代碼看起來(lái)跟同步的怎么差不多。
這次我們換個(gè)方法描述,一樣是來(lái)了用戶A和用戶B,但是先從用戶A的視角來(lái)看這件事情。
用戶A: 你好,我要讀文件first.json。
M:好的你等一下我去拿。
【M離開(kāi)了柜臺(tái)】
M:我回來(lái)了,這是你要的first.json的內(nèi)容。然后呢?
用戶A:然后把內(nèi)容按JSON解析一下。
M:你等等我去解析一下。
【M離開(kāi)了柜臺(tái)】
M:好了解析完了,然后呢?
用戶A:我要里面的someContent部分。
M:給你。再見(jiàn)。
是不是看起來(lái)跟同步一模一樣?實(shí)際上從M的角度看這件事情呢?
用戶A: 你好,我要讀文件first.json。
M:好的你等一下我去拿。
【M離開(kāi)了柜臺(tái)】
M:小P來(lái)一下!去readFile讀first.json,回來(lái)叫我。
P:好的我這就去。
【M轉(zhuǎn)向了另一個(gè)柜臺(tái)窗口】
M:你好。
用戶B:你好,我要讀文件second.json。
…………
…………
P:M,first.json拿回來(lái)了。
【M轉(zhuǎn)向第一個(gè)柜臺(tái)】
M:我回來(lái)了,這是你要的first.json的內(nèi)容。然后呢?
用戶A:然后把內(nèi)容按JSON解析一下。
M:你等等我去解析一下。
【M離開(kāi)了柜臺(tái)】
M:小P來(lái)一下!去parseJSON把這堆東西解析一下,回來(lái)叫我。
P:好的我這就去。
【M轉(zhuǎn)向了另一個(gè)柜臺(tái)窗口】
…………
…………
真相大白了,M并沒(méi)有親自去拿文件解析JSON,而是叫來(lái)了任勞任怨的小P干活,自己在用戶A面前偽裝成被占用了所有的時(shí)間的樣子,其實(shí)偷偷去接待別的用戶了。
利用Generator可以用yield中斷執(zhí)行,再在外部通過(guò)next喚醒繼續(xù)執(zhí)行的特性,co把Generator的next寫(xiě)到Promise的then里面從而實(shí)現(xiàn)循環(huán)調(diào)用。使用了co之后,代碼看起來(lái)跟同步非常相像,寫(xiě)起來(lái)符合人正常的同步思維,甚至可以使用同步的流程控制語(yǔ)句比如for。但是執(zhí)行起來(lái)卻能充分利用異步帶來(lái)的性能優(yōu)勢(shì)。
順便提一句,co看起來(lái)已經(jīng)非常像async/await方式了。Node.js中同樣近似于async/await方式的還有asyncawait庫(kù),它不依賴(lài)generator而是依賴(lài)于node-fiber,看名字大概就是Node里的一個(gè)纖程的實(shí)現(xiàn)吧。由于不需要generator,對(duì)于諸如Coffescript和Typescript類(lèi)的語(yǔ)言支持非常好。
以上就是對(duì)Javascript中最近常討論的幾種異步流程控制的簡(jiǎn)單類(lèi)比說(shuō)明。這種理解方式非常粗淺,而且有很多問(wèn)題并不像上面寫(xiě)的那樣那么簡(jiǎn)單。要想使用好異步,還是要多讀一些更為深入的文章。
參考資料Promises and Generators: Control Flow Utopia
Promises/A+
Managing Node.js Callback Hell with Promises, Generators and Other Approaches
Replacing callbacks with ES6 Generators
getiblog: Promises
異步流程控制:7 行代碼學(xué)會(huì) co 模塊
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/85899.html
摘要:從最開(kāi)始的到封裝后的都在試圖解決異步編程過(guò)程中的問(wèn)題。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。寫(xiě)一個(gè)符合規(guī)范并可配合使用的寫(xiě)一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來(lái)處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問(wèn)題描述 在開(kāi)發(fā)過(guò)程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過(guò)http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過(guò)...
摘要:在服務(wù)器端,異步模式甚至是唯一的模式,因?yàn)閳?zhí)行環(huán)境是單線程的,如果允許同步執(zhí)行所有請(qǐng)求,服務(wù)器性能會(huì)急劇下降,很快就會(huì)失去響應(yīng)。第三是,捕捉不到他的錯(cuò)誤異步編程方法回調(diào)函數(shù)這是異步編程最基本的方法。 前言 你可能知道,Javascript語(yǔ)言的執(zhí)行環(huán)境是單線程(single thread)。所謂單線程,就是指一次只能完成一件任務(wù)。如果有多個(gè)任務(wù),就必須排隊(duì),前面一個(gè)任務(wù)完成,再執(zhí)行后面...
摘要:感謝大神的免費(fèi)的計(jì)算機(jī)編程類(lèi)中文書(shū)籍收錄并推薦地址,以后在倉(cāng)庫(kù)里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡(jiǎn)介現(xiàn)在,越來(lái)越多的科技公司和開(kāi)發(fā)者開(kāi)始使用開(kāi)發(fā)各種應(yīng)用。 說(shuō)明 2017-12-14 我發(fā)了一篇文章《沒(méi)用過(guò)Node.js,就別瞎逼逼》是因?yàn)橛腥嗽谥跎虾贜ode.js。那篇文章的反響還是相當(dāng)不錯(cuò)的,甚至連著名的hax賀老都很認(rèn)同,下班時(shí)讀那篇文章,竟然坐車(chē)的還坐過(guò)站了。大家可以很...
摘要:感謝大神的免費(fèi)的計(jì)算機(jī)編程類(lèi)中文書(shū)籍收錄并推薦地址,以后在倉(cāng)庫(kù)里更新地址,聲音版全文狼叔如何正確的學(xué)習(xí)簡(jiǎn)介現(xiàn)在,越來(lái)越多的科技公司和開(kāi)發(fā)者開(kāi)始使用開(kāi)發(fā)各種應(yīng)用。 說(shuō)明 2017-12-14 我發(fā)了一篇文章《沒(méi)用過(guò)Node.js,就別瞎逼逼》是因?yàn)橛腥嗽谥跎虾贜ode.js。那篇文章的反響還是相當(dāng)不錯(cuò)的,甚至連著名的hax賀老都很認(rèn)同,下班時(shí)讀那篇文章,竟然坐車(chē)的還坐過(guò)站了。大家可以很...
閱讀 3750·2021-10-12 10:11
閱讀 2007·2019-08-30 15:53
閱讀 1614·2019-08-30 13:15
閱讀 2330·2019-08-30 11:25
閱讀 1831·2019-08-29 11:24
閱讀 1680·2019-08-26 13:53
閱讀 3551·2019-08-26 13:22
閱讀 1802·2019-08-26 10:24