避免陷入 async/await 地獄
async/await 是 ES7 的新語法。在 async/await 標準出來之前,JavaScript 的非同步編程經歷了 callback --> promise --> generator 的演變過程。在 callback 的時代,最讓人頭疼的問題就是回調地獄(callback hell)。所以,在 async/await 一經推出,社區就有人認為「這是 JavaScript 非同步編程的終極解決方案」。
但用了 async/await 就真的沒有問題了嗎?
最近閱讀了 Aditya Agarwal 的一篇文章:How to escape async/await hell。這篇文章主要討論了過度使用 async/await 導致的新的「地獄」問題,其已經在 Medium 上獲得了 19k+ 的 Applause。
好不容易逃離了一個「地獄」,又馬上陷入另一個「地獄」了。
何為 async/await 地獄
在編寫非同步代碼時,人們總是喜歡一次寫多個語句,並且在一個函數調用之前使用 關鍵字。這可能會導致性能問題,因為很多時候一個語句並不依賴於前一個語句——但使用 關鍵字後,你就需要等待前一個語句完成。
示例
假設你要寫一個訂購 pizza 和 drink 的腳本,代碼可能是如下這樣的:
這段代碼開起來沒什麼問題,也能正常的運行。但是,這並不是一個好的實現,因為這把本身可以並行執行的任務變成了串列執行。
選擇一個 drink 添加到購物車和選擇一個 pizza 添加到購物車可以看作是兩個任務,而這兩個任務之間並沒有相互依賴的關係,也沒有特定的順序執行關係。所以這兩個任務是可以並行執行的,這樣能提高性能。而上述代碼將二者變成了串列執行,顯然是降低了程序性能的。
更糟糕的例子
假設要寫一個程序,根據 followers 數用來顯示 Github 中國區用戶的排名情況。
如果只是獲取排名,我們可以調用 Github 官方的 Search users 介面,偽代碼如下:
調用 函數就能獲取到想要的結果。但是,你可能還要想要獲取每個用戶的 follower 數、email、地區和倉庫等數據,而 Search users 介面並沒有返回這些數據,你可能需要再去調用 Single user 介面。
然後上述代碼可能被改寫為:
運行查看結果,自己想要的數據都拿到了。但是,你發現一個問題,程序運行時間有點長,該怎麼優化下呢?
其實,鋪墊了這麼長,就是想說明一個問題:你陷入了 async/await 的地獄。
上述代碼的問題在於,獲取每個用戶資料的請求並不存在依賴性,就類似上文中的選擇 pizza 和 drink 一樣,這是可以並行執行的請求。而根據上述代碼,請求都變成了串列執行,這當然會損耗程序的性能。
按照上述代碼,可以看一下其非同步請求的動態圖:
GIF
images
可以看到,獲取用戶資料的每個請求都需要等到上一個請求完成之後才能執行,Waterfall 處於一個串列的狀態。那要怎麼改進這個問題呢?
既然獲取每個用戶資料的請求並不存在依賴性,那麼我們可以先觸發非同步請求,然後延遲處理非同步請求的結果,而不是一直等該請求完成。根據這個思路,那可能改進的代碼如下:
可以看一下非同步請求的動態圖:
GIF
images
可以看到,獲取用戶資料的非同步請求處理不再是串列執行,而是並行執行了,這將大大提高程序的運行效率和性能。
總結
Aditya Agarwal 在其文章中也給出了怎麼避免陷入 async/await 地獄的建議:
首先找出依賴於其他語句的執行的語句
然後將有依賴關係的一系列語句進行組合,合併成一個非同步函數
最後用正確的方式執行這些函數
參考
How to escape async/await hell
精讀《async/await 是把雙刃劍》


TAG:Pomy隨記 |