摘要:匿名函數(shù)是我們喜歡的一個(gè)重要原因,也是,它們分別消除了很多代碼細(xì)節(jié)上需要命名變量名或函數(shù)名的需要。這個(gè)匿名函數(shù)內(nèi),有更多的操作,根據(jù)的結(jié)果針對(duì)目錄和文件做了不同處理,而且有遞歸。
能和微博上的 @響馬 (fibjs作者)掰扯這個(gè)問題是我的榮幸。
事情緣起于知乎上的一個(gè)熱貼,諸神都發(fā)表了意見:
https://www.zhihu.com/questio...
這一篇不是要說(shuō)明白什么是async/await,而是闡述為什么會(huì)在編程技術(shù)這么多年后出現(xiàn)和流行了這個(gè)東西,讀懂這篇文章你需要對(duì)async/await有很透徹的機(jī)制理解。
如果是寫系統(tǒng)程序,流行的編程范式是面向?qū)ο螅@非常成熟不用多說(shuō);但如果是寫微服務(wù)(restful api server),情況不同。
寫微服務(wù)的時(shí)候數(shù)據(jù)不是從文件或數(shù)據(jù)庫(kù)讀取、去串行化、構(gòu)造對(duì)象然后在內(nèi)存中維護(hù)對(duì)象;而是向數(shù)據(jù)庫(kù)、cache、或者API提取數(shù)據(jù),計(jì)算后盡快輸出結(jié)果;
前者的數(shù)據(jù)對(duì)象生命周期較長(zhǎng),object-oriented范式很合適,它研究一個(gè)對(duì)象的狀態(tài)機(jī)和如何響應(yīng)外部事件;
后者的數(shù)據(jù)生命周期很短,而且更糟糕的,各種input數(shù)據(jù)的結(jié)構(gòu)也不很穩(wěn)定,經(jīng)常變化,所以這個(gè)時(shí)候OO的模式就顯得笨重和低效了,在這個(gè)時(shí)候?qū)ata的處理不是object-oriented范式,而是transformation-oriented范式。
后者導(dǎo)致了函數(shù)式編程的興起,這里沒法仔細(xì)討論函數(shù)式編程的方方面面,我們僅僅說(shuō)transformation的問題。
這種編程范式下一次api服務(wù)的生命周期在心理模型上一個(gè)函數(shù)的開始和結(jié)束,這個(gè)函數(shù)需要從很多地方pull數(shù)據(jù),如果是從內(nèi)存中直接pull,這個(gè)在fp里叫做state monad;如果是異步pull數(shù)據(jù),包括文件、數(shù)據(jù)庫(kù)、其他api,這個(gè)叫io monad。
OO的本質(zhì)站在fp的角度看是如何維護(hù)state monad,如果程序中有stateful的部分,或多或少都會(huì)有,用oo建模不是問題;訪問這些state都是同步的也不是問題;
async/await的出現(xiàn)是為了解決第二個(gè)問題,io monad。
在采用transformation和fp方式寫微服務(wù)的時(shí)候,常見情況不是處理單一數(shù)據(jù)單元,而是數(shù)據(jù)集合,集合數(shù)據(jù)的變換是map/filter,聚合是reduce(廣義);這個(gè)過(guò)程可以有條件,可以是nested,其結(jié)構(gòu)取決于你的業(yè)務(wù)邏輯和solution model,不是編程技術(shù)解決的。
所以你大體可以把這些邏輯先用同步的方式寫出來(lái),假定所有異步獲得的數(shù)據(jù)都可以同步獲得,然后把需要pull的數(shù)據(jù)改成用async/await去獲取;這在結(jié)構(gòu)上很清晰;
在這個(gè)時(shí)候開發(fā)者考慮的問題不是如何對(duì)付單一數(shù)據(jù)的異步獲取問題,而是考慮這些異步過(guò)程之間如何去串行和并發(fā)的問題;換句話說(shuō),他們的執(zhí)行序是你要program的邏輯的一部分。既然他們是programming邏輯的一部分,那么他們顯示存在就理所應(yīng)當(dāng)。
這里說(shuō)的串行和并發(fā)僅指從io monad里pull數(shù)據(jù)的操作,不是指程序中其他部分的執(zhí)行體之間的并發(fā)或并行。下同。
這里有兩個(gè)平衡:
第一:如果要追求service time越短越好,也就是提高響應(yīng)時(shí)間,那么這些異步就會(huì)象project軟件里的甘特圖一樣,能并發(fā)的盡早并發(fā),service time取決于最長(zhǎng)的路徑。通常瓶頸都是io不是算力,除非設(shè)計(jì)有問題或者算法寫得太爛。
這種優(yōu)化很可能帶來(lái)代碼結(jié)構(gòu)的不清晰,但是它是可以做而且容易做的,在async/await模式下,因?yàn)樗诖a層面上基本上保留了這個(gè)甘特圖關(guān)系。
它適應(yīng)業(yè)務(wù)變化的能力也很好,在業(yè)務(wù)邏輯變化必須修改的時(shí)候,開發(fā)者總有一個(gè)比較清除的甘特圖,如果你不在async子函數(shù)里封裝太長(zhǎng)的不必要邏輯的話;和OO建模時(shí)我們反復(fù)問一個(gè)對(duì)象是不是single responsibility一樣,一個(gè)async函數(shù)的封裝越原子化,越容易讓開發(fā)者在上層組合順序和并發(fā)。
這里我不去批判thread或者fiber或者goroutine或者coroutine的模型,只強(qiáng)調(diào)異步數(shù)據(jù)的pull邏輯的原子化,這是高并發(fā)微服務(wù)編程對(duì)開發(fā)者提出來(lái)的新問題,原則上任何一種開發(fā)語(yǔ)言和開發(fā)模型都可以做到對(duì)等的性能和可用性,但實(shí)踐上大多數(shù)情況下,程序員不把program異步pull數(shù)據(jù)的順序和并發(fā)當(dāng)成是自己編程邏輯的一部分,去享受thread model下的編程邏輯簡(jiǎn)單,這是不對(duì)的;你可以有理由不急著去做service time優(yōu)化,但是不意味著你根本不知道它的模型邏輯和如果要去優(yōu)化,做法是什么。
第二:async函數(shù)對(duì)gc的壓力很大,因?yàn)閏ompiler很難去判斷在運(yùn)行時(shí)哪些域內(nèi)變量可以回收,這不同于閉包變量,閉包變量的生命周期判斷在源碼級(jí)的詞法域就可以分析出來(lái);所以async函數(shù)的執(zhí)行應(yīng)該是短生命周期的。
例子貼一小段代碼,實(shí)際項(xiàng)目代碼,沒什么特別的,Promise用了bluebird庫(kù):
async storeDirAsync(dir) { let entries = await fs.readdirAsync(dir) let treeEntries = await Promise .map(entries, async entry => { let entryPath = path.join(dir, entry) let stat = await fs.lstatAync(entryPath) if (stat.isDirectory()) return ["tree", entry, await this.storeDirAsync(entryPath)] if (stat.isFile()) return ["blob", entry, await this.storeFileAsync(entryPath)] return null }) .filter(treeEntry => !!treeEntry) return await this.storeObject(treeEntries) }
這是一個(gè)class方法。
它的第一步是獲取了一個(gè)文件夾內(nèi)的entries,然后用Bluebird庫(kù)提供的map方法應(yīng)用了一個(gè)async函數(shù)上去,這是個(gè)匿名函數(shù)。
匿名函數(shù)是我們喜歡fp的一個(gè)重要原因,functor chaining也是,它們分別消除了很多代碼細(xì)節(jié)上需要命名變量名或函數(shù)名的需要。
這個(gè)匿名函數(shù)內(nèi),有更多的await操作,根據(jù)fs.stat的結(jié)果針對(duì)目錄和文件做了不同處理,而且有遞歸。async之內(nèi)是順序執(zhí)行的,但async在map里是并發(fā)的,這些東西都顯式擺在代碼層面上。
如果任務(wù)范圍更大,你可以把很多promise聚合在盡可能早的時(shí)候并發(fā)。
當(dāng)然這個(gè)寫法沒有美好到可以直接寫entries.mapAsync()的程度,但基本上做到了上述的要求:在源碼層面上對(duì)順序和并發(fā)有一覽,有控制,容易變更。
說(shuō)到底,async是讓這種順序和并發(fā)的書寫和維護(hù)變得容易,而不是說(shuō)我不要寫并發(fā),一切順序走;但是反過(guò)來(lái)說(shuō)它的效率不是最好的,在node里最好的效率目前和可見的未來(lái)都是裸寫callback,那是最后的性能優(yōu)化了。
最后我們說(shuō)這個(gè)寫法的一個(gè)有點(diǎn)麻煩的坑。
在class方法里寫async有個(gè)this binding的問題,搞出來(lái)一個(gè)閉包變量并不是最好的辦法,Bluebird庫(kù)里有Promise.bind方法解決這個(gè)問題,上述代碼中用arrow function的lexical scope bind this也是一個(gè)辦法(也是推薦的辦法)。
總結(jié)node.js是我寫過(guò)的最好的純粹event model模型的開發(fā)環(huán)境;遠(yuǎn)好過(guò)天生thread模型倒回來(lái)打很多non-blocking補(bǔ)丁的做法;
javascript領(lǐng)域,和目前整個(gè)編程界,在使用asynchronous(異步)這個(gè)詞來(lái)說(shuō)我們?cè)谶@篇文章里聊的問題,這是個(gè)錯(cuò)誤,asynchronous在編程上有其他含義,無(wú)論是寫系統(tǒng)程序(signal handler)還是寫內(nèi)核或者裸金屬(isr);這個(gè)問題的準(zhǔn)確表述是:non-blocking。
而對(duì)應(yīng)non-blocking的solution模型是如何調(diào)度(schedule)執(zhí)行體;再然后的問題轉(zhuǎn)換成你需要顯式調(diào)度還是隱式調(diào)度?
如果你認(rèn)為:
service time是需要追求的
調(diào)度邏輯是經(jīng)常隨著業(yè)務(wù)邏輯變化而變化的
完整的數(shù)據(jù)流變換邏輯和調(diào)度邏輯都應(yīng)該在代碼層面上呈現(xiàn)總覽,是top-down的構(gòu)建的
你應(yīng)該選擇async/await;
反之,你希望編程極致簡(jiǎn)單,調(diào)度不在你的solution模型之內(nèi),你bottom-up構(gòu)建邏輯,應(yīng)該遠(yuǎn)離javascript,選擇thread模型。
白潔“請(qǐng)把你的左手放在自己的大咪咪上,回答一個(gè)問題,調(diào)度執(zhí)行體和調(diào)度io是一回事嗎?”
白潔搖搖頭。
“我也認(rèn)為不是,但是很多runtime library并沒有區(qū)分兩者。” said I.
JavaScript的event model并沒有所謂的調(diào)度執(zhí)行體的設(shè)計(jì),它本質(zhì)上只有調(diào)度io。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/83225.html
摘要:的科學(xué)定義是或者,它的標(biāo)志性原語(yǔ)是。能解決一類對(duì)語(yǔ)言的實(shí)現(xiàn)來(lái)說(shuō)特別無(wú)力的狀態(tài)機(jī)模型流程即狀態(tài)。容易實(shí)現(xiàn)是需要和的一個(gè)重要原因。 前面寫了一篇,寫的很粗,這篇講講一些細(xì)節(jié)。實(shí)際上Fiber/Coroutine vs Async/Await之爭(zhēng)不是一個(gè)簡(jiǎn)單的continuation如何實(shí)現(xiàn)的問題,而是兩個(gè)完全不同的problem和solution domain。 Event Model 我...
摘要:我們已經(jīng)回答了的構(gòu)造函數(shù)和原型都是誰(shuí)的問題,現(xiàn)在牽扯出來(lái)一個(gè),我們繼續(xù)檢查的構(gòu)造函數(shù)是全局對(duì)象上屬性叫的對(duì)象的原型是個(gè)匿名函數(shù),按照關(guān)于構(gòu)造函數(shù)的約定,它應(yīng)該是構(gòu)造函數(shù)的屬性我們給這個(gè)對(duì)象起個(gè)名字,叫。 我不確定JavaScript語(yǔ)言是否應(yīng)該被稱為Object-Oriented,因?yàn)镺bject Oriented是一組語(yǔ)言特性、編程模式、和設(shè)計(jì)與工程方法的籠統(tǒng)稱謂,沒有一個(gè)詳盡和大家...
摘要:一般這種情況會(huì)在類的構(gòu)造函數(shù)內(nèi)創(chuàng)建一個(gè)屬性,引用或詞法域的,但后面會(huì)看到我們有更好的辦法,避免這種手工代碼。 換句話說(shuō),StateUp模式把面向?qū)ο蟮脑O(shè)計(jì)方法應(yīng)用到了狀態(tài)對(duì)象的管理上,在遵循React的組件化機(jī)制和基于props實(shí)現(xiàn)組件通訊方式的前提之下做到了這一點(diǎn)。 ---- 少婦白潔 閱讀本文之前,請(qǐng)確定你讀過(guò)React的官方文檔中關(guān)于Lifting State Up的論述: ht...
摘要:目的是為了解決在重用的時(shí)候,持久和方法重用的問題。換句話說(shuō)你不用擔(dān)心把組件寫成模式不好重用,如果你需要傳統(tǒng)的方式使用,一下即可。 這篇文章所述的思想最終進(jìn)化成了一個(gè)簡(jiǎn)單的狀態(tài)管理模式,稱React StateUp Pattern,詳細(xì)介紹請(qǐng)參閱:https://segmentfault.com/a/11... 寫了一個(gè)非常簡(jiǎn)單的實(shí)驗(yàn)性Pattern,暫且稱為PurifiedCompon...
摘要:本文用于闡述模式的算法和數(shù)學(xué)背景,以及解釋了它為什么是里最完美的狀態(tài)管理實(shí)現(xiàn)。歡迎大家討論和發(fā)表意見。 本文用于闡述StateUp模式的算法和數(shù)學(xué)背景,以及解釋了它為什么是React里最完美的狀態(tài)管理實(shí)現(xiàn)。 關(guān)于StateUp模式請(qǐng)參閱:https://segmentfault.com/a/11... P-State, V-State 如果要做組件的態(tài)封裝,從組件內(nèi)部看,存在兩種不同的...
閱讀 3295·2021-11-25 09:43
閱讀 2098·2021-09-22 10:02
閱讀 3361·2021-09-06 15:00
閱讀 2309·2019-08-30 15:56
閱讀 2363·2019-08-30 15:54
閱讀 3238·2019-08-30 14:14
閱讀 2272·2019-08-29 17:25
閱讀 2915·2019-08-29 17:16