JavaScript通常一次只處理一件工作。同步和非同步的差別,在於程式遇到需要等待的工作時,要停在原地等待,還是先繼續處理後面的程式。
同步可以想成在櫃台排隊,前一個人還沒處理完成,下一個人就不能開始。程式會按照撰寫順序一行一行執行,前一行完成後才會進入下一行:
console.log("1. 開始計算");
const price = 100;
const amount = 3;
const total = price * amount;
console.log(`2. 總金額是 ${total} 元`);
console.log("3. 計算結束");

結果會按照 1、2、3 的順序出現。計算總金額時,後面的console.log()會等待計算完成,這就是同步執行。
不過有些工作不會立刻完成,例如等待計時器、讀取檔案或向伺服器取得資料。如果程式一直停在原地等待,網頁上的其他操作也可能被卡住。這類需要等待的工作可以使用非同步方式處理。
非同步可以想成在餐廳點餐。完成點餐後會先拿到號碼,不需要站在櫃台前一直等,可以先去做其他事情,餐點完成後再通知取餐。
這裡先用setTimeout()模擬一個需要等待的工作:
console.log("1. 開始等待");
setTimeout(() => {
console.log("3. 等待完成");
}, 1000);
console.log("2. 先執行其他程式");
setTimeout()會設定一個計時器,1000 毫秒等於 1 秒。傳入的函式不會立刻執行,而是等時間到後再執行。這種稍後才執行的函式稱為 callback,也就是回呼函式。
因此輸出順序會是「1. 開始等待」、「2. 先執行其他程式」,約一秒後才出現「3. 等待完成」。這裡的重點不是三段程式同時執行,而是 JavaScript先把需要等待的計時器交給瀏覽器或執行環境,再繼續處理目前的同步程式。
等計時器完成後,回呼函式會先進入工作佇列排隊。事件迴圈會持續確認目前的同步程式是否已經執行完畢,等目前工作清空後,才把排隊中的回呼函式交給 JavaScript執行。
所以就算等待時間改成 0,計時器中的內容仍然會排在目前的同步程式後面:
console.log("A");
setTimeout(() => {
console.log("C");
}, 0);
console.log("B");
結果仍然是 A、B、C。非同步適合處理需要等待的工作,但一般的計算和迴圈仍然是同步執行。