問題背景:
在公司的一個Node.js項目中,在async方法內部,需要調用另外一個async方法進行大文本的正則匹配(耗時非常久),之前以為只要是不添加await關鍵字,這個方法就可以自動的異步進行調用。但實際上,每當訪問這個接口時,響應還是非常的緩慢。
Node的事件模型
如上圖所示,每個請求到Node的時候,程序會把請求方法與其它方法放入一個事件隊列中,然后在Node的主線程中重復循環處理函數,當遇到阻塞時(一般是磁盤IO、數據庫IO、網絡IO等),主線程會跳到下一個函數執行。而那個等待IO的函數則由線程池進行監聽,當子線程監聽到完成事件,就把該回調函數放入事件循環隊列中,讓主線程進行回調。
問題的理解與解答
Promise的認知
上面提到了,以不添加await的形式調用async方法。實際上,await和async是對Promise的一個語法糖的體現,其中用async聲明的函數本身就是一個Promise對象,而在調用時添加await方法大致等同於在Promise后面添加一個then()的回調函數來處理Promise的結果。
解答
根據上面的描述,只要調用async不添加await關鍵字的方法,那么這個方法就會異步的執行,而本次事件的流程會繼續往下走。那么,為什么在調用非常耗時的操作后,整個應用程序會變得異常緩慢呢?
我是這么理解的:Promise只是一個異步方法的容器,僅僅標明這個方法是異步的、一定會有結果的。但是它並不會調用線程池的線程去執行這個Promise,總的來說就是:這個Promise是由Node的主線程去執行的。這就會造成一個問題,即便當前調用耗時操作的方法沒有因此阻塞,但是后續事件循環到這個耗時操作時,Node主線程還是得去處理,一旦線程處理這個耗時操作,那么整個應用程序的其它請求就無法得到響應。
實際解決方式
根據上述描述,我們需要一個真正的異步線程去執行我們的耗時函數,這個時候可以用到的模塊有:
Worker threads, child processes, clusters, job queues
具體的實現可以根據Node的api文檔進行。
此外,如果還不太理解上述內容,可以訪問下面幾條鏈接進一步了解:
這兩個是問答,有非常直接的答案:
https://stackoverflow.com/questions/46004290/will-async-await-block-a-thread-node-js
https://www.reddit.com/r/node/comments/a5a7fe/how_to_do_work_asynchronously/
介紹耗時操作如何阻塞整個事件循環,雖然和這個問題不太相關,但是寫的還不錯:
https://snyk.io/blog/nodejs-how-even-quick-async-functions-can-block-the-event-loop-starve-io/