筆者一開始看到這兩個詞的時候充滿著黑人問號???
同步不是應該表示可以同時處理多件事,
而非同步不是應該表示一次只能處理一件事嗎?
怎麼實際上跟我原本的理解完全相反!!!
在 JS 的世界裡,JS 的本質為 Sync,他的意思表示一次只能做一件事。
JS 使用的資料結構為 stack 堆疊(後進後出),需要等上層的事件做完才能做下層的事件。
如果用函式讓他一直循環,到最後記憶體會滿出來,出現 Ranger Error,
稱做 stack overflow,跟工程師圈裡知名的查找問題的網站同名。
跟無窮迴圈不太一樣,無窮迴圈只會卡住,不會噴錯誤訊息。
以下的範例是 JS 遇到無窮迴圈的狀況:
1.因為同步的關係,JS 由上而下一次只能做一件事,console.log('JS')會加到 stack 裡,並馬上拿出執行,印出 JS。
2.而執行到迴圈時也會加到 stack 裡,因為此範例是無窮迴圈,所以會一直執行,造成卡住的狀態。
3.卡住後無法添加 console.log('Ruby') 到 stack 裡,沒辦法印出後面的 Ruby。
console.log('JS');
for(let a = 1; a > 0; a++) {
console.log("hi")
}
console.log('Ruby');
JS
hi(一直執行)
不會通通都卡在 Stack 區塊,把 JS 自己該做的事做完再做瀏覽器的事。
非 JS 內建語法,如 Web API (註解[3]) 的 setTimeout callback 不會卡在 stack 區塊裡。
一開始會先在 stack 區塊出現,接著瞬間丟給 Web API Runtime(執行環境) 執行,
等設定的時間過了之後來到 Queue 排隊區(會按照順序排列),
最後等 stack 區塊裡的程式碼跑完,setTimeout callback 才會跑到 stack 區塊執行結果。
詳細流程解說可參考註解[4]。
以下的範例是 JS 遇到 Web API 的 setTimeout 的狀況:
1.JS 會先印出來。
2.因為非同步的關係,setTimeout 函式會被丟到 Web API Runtime 執行。
3.Ruby 會被印出來。
4.過一秒後 Python 被印出。
console.log('JS');
setTimeout(() => {
console.log("Python")}
, 1000);
console.log('Ruby');
JS
Ruby
Python
或許你會想問如果我把 setTimeout 後面的時間改成 0 秒會不會改變結果。
答案是...不會
因為他是 Web API 的函式,一開始丟到 stack 後還是會被丟到 Web API Runtime 執行,
接著來到 Queue 排隊區,最後回到 JS 被執行,
這是執行順序的問題,不是時間的問題。
參考:
[1]MDN:非同步的 JavaScript 介紹
[2]Data Structure with JavaScript: Stacks
[3]MDN:Web APIs
[4]Loupe