為什麼async/await關鍵字是如此重要

發表於2018-09-13

現在JS裡有async/await了,處理非同步程式碼幾乎不再有什麼爭議,但還是會有人有疑問,為什麼不把所有函式都定義成async的,然後所有函式呼叫都寫成await的,這樣最終不就可以省略掉所有的async/await關鍵字了嗎(預設隱式async/await)?這樣不就達成了“天下無非同步”的太平盛世了嗎?

只要稍微動點腦筋就不會有這種想法。

我們都知道目前的環境下JS它還是一門單執行緒的語言,然後通過Event Loop來實現非同步IO。雖然也有fibjs這種“異類”,會稍微打破一些認知。基於這個前提,我們就有一些共識,比如:

  • 同一個event裡的程式碼是順序執行的不可分割的單元,在這裡就不需要考慮資源競爭的問題了。
  • 通過callback或者promise方式呼叫的東西會受到Event Loop的排程,不管它是Macro Task還是Micro Task,反正會進入另一個單元裡執行。

那麼有如下程式碼

如果單看這兩個函式,如果ABfoobar都沒有副作用,那麼會覺得這兩個函式的效果沒什麼差別,在這種情況下“預設async/await”似乎是可行的。

但如果有共享資源和競爭,事情就會變得完全不一樣。

如果AB用到了共享資源,對於A而言,因為是完全同步執行的,那麼整個A的程式碼會在一個event裡執行,它是“執行緒安全”的(這裡加引號的是因為這個不是嚴格的執行緒安全的概念,只是表示個意思),只要通過靜態程式碼分析的手段就可以得知foobarshared有沒有副作用,那麼這個A函式的執行結果就是可預知的。

B則完全不一樣,因為await關鍵字會出讓執行權,也就是foobarreturn不在一個event裡執行,那麼在這三行程式碼的“行縫兒”之間就有無數的可能性,這些縫隙裡塞進去一萬個event也不得而知。這種情況下對foobar執行靜態分析(去判斷他們對shared有沒有副作用)是沒什麼卵用的,因為shared被修改的可能性有無數種,比如觸發了一個事件導致別的listener修改了shared。也就是說B函式的執行結果是不能通過靜態分析而預知的,它不再是純函式了(廢話)。

這就是async/await的重要性了,它絕對不是一個簡單的語言設計的品味問題,不全域性省略async/await是因為它明確的告訴寫程式碼的人這個地方會發生什麼事情。開發者只要看到它,馬上就會對這裡的共享資源多提一個心眼,會以完全不一樣的眼光去看待B函式。

而對於嚴肅地寫程式碼、寫嚴肅的程式碼而言,“知道一行程式碼會發生什麼”這件事有多重要我想不需要再多強調了。

相關文章