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