在介紹同步(synchronous)與非同步(asynchronous)前一定要來破個題,那就是
JavaScript 的執行期(runtime)一定是有按行數順序的即時編譯。
可是為何有時執行的函式會稍晚的時候才輸出、執行?
會有這個疑問就讓我們一起來認識認識什麼是同步與非同步吧!
每個 JavaScript 程式區塊在執行時的步驟可分為呼叫與得到回應,而簡單來說能立即得到回應結果的就是同步(Synchronous),不能立即得到回應的就是非同步(Asynchronous),也就是說同步與非同步辨認的方式可以透過會不會阻塞(blocking)主程式來判斷。
Synchronous 同步
同步的 JavaScript 函式最大的特色就是會阻塞主程式:
1 | var money = 0; |
你會發現控制台依序輸出了:
1 | 'do something' |
這也是我們一般預期寫完程式該要有的順序,然而有時候會有不同的情況發生。
Asynchronous 非同步
非同步的 JavaScript 函式最大的特色就是不會阻塞主程式,
將上面程式碼範例以非同步的方法撰寫:
1 | var money = 0; |
你會發現輸出的順序變成了:
1 | 'do something' |
現在你會知道了有同步跟非同步兩種寫法,但為何我們需要非同步,以及要怎麼寫非同步的函式呢?
為何需要非同步?
之所以在 JavaScript 中會需要使用到非同步的用法原因是 JavaScript 的執行期(runtime)本身是單執行緒(single thread)的,因此若在執行程式時遇到需要長時間執行的程式就會使整個 JavaScript 執行阻塞。
而藉任務循環(Event Loop)的機制,JavaScript 會將其函式交由任務序列(Task Queue)來處理,而一些比較常見被定義為非同步的函式如下:
XMLHttpRequest
:預設採用非同步方式取得資料,它另外也提供同步的方式給需要的開發者。fetch
:使用非同步的做法取得資料。setTimeout
、setInterval
:由於 JavaScript 本身沒有計時器,因此執行後會交由瀏覽器的計時器倒數,時間到了才回傳至任務序列(Task Queue)等待任務循環(Event Loop)機制將其傳入主線程。promise
怎麼寫非同步的函式?
JavaScript 工程師其實沒辦法自己定義非同步。
沒錯,JavaScript 本身並沒有提供給你一個機制去定義,準確得來說是依照遵循 ECMA 的規則,由各瀏覽器去實作裡面的內容。
如果需要創建一個非同步的程式區塊,我們可以藉由原本就是被定義為非同步的函式搭配使用,而最快的方式就是 setTimeout(fn, 0)
:
1 | console.log('開始執行主程式') |
可以看見輸出結果為:
1 | '開始執行主程式' |
這裡重點在於 setTimeout
本身是非同步的,所以在 JavaScript 執行時期(runtime)中此程式區塊會先在瀏覽器中 API 中的 Timer
等候 0
秒,接著將其中的 callback function 排入任務隊列中,等待任務循環輪詢時執行。
另一個重點則是並非因為只需要等待 0
秒就會是同步的程式區塊。
如果需要更詳細的解釋可以參考:
詳見 回呼函式(Callback Function)一文
詳見 Event Loop
一文
詳見 Promise
一文
詳見 Async
/ Await
一文