Skip to content

2026-05-14

/ 13 分鐘閱讀

/ JavaScript Hard Parts v3

Callback Queue 與執行規則:非同步回呼如何回到 JavaScript ​

JavaScript 是一個高度可預測的環境,同步執行意味著每一行程式碼的執行順序都是確定的。然而,一旦開始與瀏覽器外部功能互動(如計時器、網路請求),外部世界的不確定性就會介入,回應可能快、可能慢、也可能永遠不來。為了保住 JavaScript 的可預測性,必須有一套嚴格的規則來管理「外部工作完成後,如何安全地回到 JavaScript 執行」。

Callback Queue 登場 ​

以下面的程式碼為例:

javascript
function printHello() {
    console.log('Hello');
}

function blockFor1Sec() {
    // 在 JavaScript 執行緒中阻塞整整 1 秒(如複雜的 for loop)
}

setTimeout(printHello, 0);
blockFor1Sec();
console.log('Me first!');

0ms:宣告兩個函式 ​

printHello 與 blockFor1Sec 的函式定義被存入全域記憶體。

0ms:呼叫 setTimeout ​

setTimeout 作為幻象函式,在瀏覽器端啟動一個 0ms 計時器,並附帶 printHello 的函式參照作為完成後要執行的回呼。

0ms 計時器幾乎立刻完成。 那麼 printHello 應該馬上被放回 Call Stack 執行嗎?

答案是否定的。printHello 不會直接回到 Call Stack,而是被放入一個新的結構:Callback Queue(又稱 Task Queue),在那裡等候。

1ms:執行 blockFor1Sec ​

JavaScript 繼續往下同步執行,呼叫 blockFor1Sec(),將其加入 Call Stack,建立新的執行環境,並在 JavaScript 主執行緒中連續運算整整 1000ms。

在這 1000ms 期間,printHello 雖然早已在 Callback Queue 中等待,但它完全無法插隊,因為 Call Stack 並非空的。

1001ms:blockFor1Sec 結束 ​

blockFor1Sec 從 Call Stack 中彈出,回到全域。此時 Call Stack 雖然空了,但全域程式碼尚未執行完畢。

printHello 仍然不能執行。

1001ms:執行 console.log ​

JavaScript 繼續執行全域程式碼的最後一行:

Me first!

1002ms:全域程式碼執行完畢,printHello 終於執行 ​

所有全域程式碼跑完後,Call Stack 清空,JavaScript 才從 Callback Queue 中取出 printHello,加入 Call Stack,建立新的執行環境,輸出:

Hello

核心規則:Callback Queue 的執行條件 ​

這個執行順序並非巧合,而是由一條嚴格的規則決定的:

Callback Queue 中的函式,必須等到所有全域程式碼執行完畢、Call Stack 完全清空後,才能被取出並加入 Call Stack 執行。

這條規則看似嚴苛,但它保障了 JavaScript 的根本可預測性:全域程式碼永遠會完整跑完,不會在任意時刻被非同步的回呼函式打斷。無論 Callback Queue 中有多少函式在等待,它們都只能在全域執行結束後才有機會運行。

負責監控「Call Stack 是否清空、Callback Queue 是否有待執行的函式」,並決定何時把回呼送上 Call Stack 的機制,就是 Event Loop,這將在後續文章中說明。

複習 ​

當 setTimeout 的延遲設定為 0 毫秒時,它的 callback 會立刻被放到 Call Stack 上執行嗎? ​

不會。即使延遲為 0 毫秒,callback function 也不會直接進入 Call Stack,而是被放入 Callback Queue(又稱 Task Queue)等候,直到所有全域程式碼執行完畢後,才能被加入 Call Stack 執行。

Callback Queue(Task Queue)是什麼?它在 JavaScript 執行模型中的作用是什麼? ​

Callback Queue 是外部世界(例如 Web Browser 功能)與 JavaScript 引擎之間的介面。來自非同步操作、已準備好執行的函式會被放入此佇列,等候所有全域程式碼執行完畢後,才能被加入 Call Stack 執行。

Web Browser APIs 是什麼?它們與 JavaScript 執行的關係是什麼? ​

Web Browser APIs(API 為 Application Programming Interface 的縮寫)是由瀏覽器提供、存在於 JavaScript 執行環境之外的功能。它們讓 JavaScript 得以使用計時器、網路請求、DOM 操作等瀏覽器能力。setTimeout 等函式是幻象函式(Facade Functions),作為 JavaScript 與這些瀏覽器功能之間的介面。

決定 Callback Queue 中的函式何時可以執行的嚴格規則是什麼? ​

Callback Queue 中的函式必須等到所有全域程式碼執行完畢、Call Stack 完全清空後,才能被加入 Call Stack 執行。這意味著,即使一個 callback 立即就緒(例如 0ms 計時器),它也必須等候所有同步的全域程式碼執行完成後才能運行。

當 setTimeout 以函式和延遲值被呼叫時,會發生什麼事?請描述 JavaScript 與瀏覽器之間的流程。 ​

setTimeout 被呼叫時,它把函式定義(不是呼叫它)和延遲值傳給瀏覽器的計時器功能。瀏覽器在背景啟動計時器。計時器完成後,該函式被放入 Callback Queue(不是直接放到 Call Stack),在那裡等候所有全域程式碼執行完畢後才能運行。

小測驗 ​

Callback Queue(Task Queue)在 JavaScript 中的作用是什麼? 它存放來自 Web Browser APIs 的函式,等候所有全域程式碼執行完畢後才能執行
在程式碼範例中,`printHello` 的「Hello」和全域程式碼的「Me first」,哪一個先被輸出到 console? 「Me first」
在 JavaScript 中呼叫 `setTimeout` 時會發生什麼事? 它在 Web Browser 中觸發一個計時器,並儲存要稍後執行的 callback function
Callback Queue 中的函式可以被執行的基本規則是什麼? 只有在 Call Stack 清空且所有全域程式碼執行完畢後,才能執行

此文章是 FrontendMasters 上的 The Hard Parts of JavaScript 課程筆記

最後更新時間:

Buy Me A Coffee

系列章節 第 29 篇 / 共 50 篇

0 %
MIT Licensed | Copyright © 2025-present Wen-Hsiu's Blog
Photo by Federica Galli on Unsplash