現在JS裡有async/await
了,處理非同步程式碼幾乎不再有什麼爭議,但還是會有人有疑問,為什麼不把所有函式都定義成async
的,然後所有函式呼叫都寫成await
的,這樣最終不就可以省略掉所有的async/await
關鍵字了嗎(預設隱式async/await
)?這樣不就達成了“天下無非同步”的太平盛世了嗎?
只要稍微動點腦筋就不會有這種想法。
我們都知道目前的環境下JS它還是一門單執行緒的語言,然後通過Event Loop來實現非同步IO。雖然也有fibjs這種“異類”,會稍微打破一些認知。基於這個前提,我們就有一些共識,比如:
- 同一個event裡的程式碼是順序執行的不可分割的單元,在這裡就不需要考慮資源競爭的問題了。
- 通過callback或者promise方式呼叫的東西會受到Event Loop的排程,不管它是Macro Task還是Micro Task,反正會進入另一個單元裡執行。
那麼有如下程式碼
1 2 3 4 5 6 7 8 9 |
function A() { foo() bar() } async function B() { await foo() await bar() } |
如果單看這兩個函式,如果A
、B
、foo
、bar
都沒有副作用,那麼會覺得這兩個函式的效果沒什麼差別,在這種情況下“預設async/await
”似乎是可行的。
但如果有共享資源和競爭,事情就會變得完全不一樣。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var shared = 0 function A() { foo() bar() return shared++ } async function B() { await foo() await bar() return shared++ } |
如果A
和B
用到了共享資源,對於A
而言,因為是完全同步執行的,那麼整個A
的程式碼會在一個event裡執行,它是“執行緒安全”的(這裡加引號的是因為這個不是嚴格的執行緒安全的概念,只是表示個意思),只要通過靜態程式碼分析的手段就可以得知foo
和bar
對shared
有沒有副作用,那麼這個A
函式的執行結果就是可預知的。
但B
則完全不一樣,因為await
關鍵字會出讓執行權,也就是foo
、bar
、return
不在一個event裡執行,那麼在這三行程式碼的“行縫兒”之間就有無數的可能性,這些縫隙裡塞進去一萬個event也不得而知。這種情況下對foo
和bar
執行靜態分析(去判斷他們對shared
有沒有副作用)是沒什麼卵用的,因為shared
被修改的可能性有無數種,比如觸發了一個事件導致別的listener修改了shared
。也就是說B
函式的執行結果是不能通過靜態分析而預知的,它不再是純函式了(廢話)。
這就是async/await
的重要性了,它絕對不是一個簡單的語言設計的品味問題,不全域性省略async/await
是因為它明確的告訴寫程式碼的人這個地方會發生什麼事情。開發者只要看到它,馬上就會對這裡的共享資源多提一個心眼,會以完全不一樣的眼光去看待B
函式。
而對於嚴肅地寫程式碼、寫嚴肅的程式碼而言,“知道一行程式碼會發生什麼”這件事有多重要我想不需要再多強調了。