Node.js事件循环执行顺序异常及阶段执行逻辑疑问咨询
First, let's recap your test code and observed output for context:
Test Code
setTimeout(function() { console.log("Timeout 1"); }, 0); fs.readFile("test-file-empty.txt", () => { console.log("File 1"); }); setTimeout(function() { console.log("Timeout 2"); }, 100); setImmediate(function() { console.log("Immediate 1"); });
Observed Output
Timeout 1 Immediate 1 File 1 Timeout 2
Let's break down your questions step by step to clear up the confusion around Node.js's event loop.
1. Correcting Your Tick Sequence Understanding
Your initial breakdown of the ticks isn't quite accurate. Let's map the execution flow to Node.js's event loop stages (Timers → Pending Callbacks → Idle/Prepare → Poll → Check → Close Callbacks):
First Tick:
- All synchronous code runs first, queuing each async task to its respective stage:
setTimeout(..., 0)goes to the Timers queue (note: Node.js enforces a minimum 1ms delay forsetTimeout(0)under the hood).fs.readFileinitiates an async I/O operation; its callback will only be queued to the Poll stage once the OS finishes handling the file read.setTimeout(..., 100)goes to the Timers queue.setImmediategoes directly to the Check stage queue.
- Enter the Timers stage: Execute the
setTimeout(..., 0)callback → outputsTimeout 1. - Pass through Pending Callbacks, Idle/Prepare stages (no tasks are waiting here).
- Enter the Poll stage: At this point, the
fs.readFileI/O operation hasn't finished yet—even with an empty file, OS-level I/O scheduling takes a tiny amount of time that outpaces the first loop iteration. Since there's a task waiting in the Check stage (setImmediate), the Poll stage exits immediately instead of waiting for I/O to complete. - Enter the Check stage: Execute the
setImmediatecallback → outputsImmediate 1. - The Close Callbacks stage has no tasks, so the first tick ends.
- All synchronous code runs first, queuing each async task to its respective stage:
Second Tick:
- Back to the Timers stage: The 100ms delay for the second
setTimeouthasn't elapsed yet, so no tasks run here. - Pass through Pending Callbacks, Idle/Prepare stages (no tasks).
- Enter the Poll stage: Now the OS has finished the
fs.readFileoperation, and its callback is waiting in the Poll queue. Execute it → outputsFile 1. - With the Poll queue empty, Node.js checks if any Timers have expired. The 100ms delay has now elapsed, so the loop moves back to the Timers stage for the next tick.
- Back to the Timers stage: The 100ms delay for the second
Third Tick:
- Enter the Timers stage: Execute the
setTimeout(..., 100)callback → outputsTimeout 2.
- Enter the Timers stage: Execute the
To summarize: Your initial tick grouping was incorrect. The execution spans 3 distinct ticks, each covering a full pass through the event loop's stages.
2. Why setImmediate Runs Before fs.readFile Callback
The core reason boils down to when the I/O callback gets queued. Even though your test file is empty, asynchronous file I/O isn't instant—it depends on the operating system to handle the read operation, which takes a small but non-zero amount of time.
Here's the play-by-play:
- During the first tick's Poll stage, the
fs.readFileoperation hasn't completed yet, so its callback isn't in the Poll queue. - Node.js detects there's a task waiting in the Check stage (
setImmediate), so it skips waiting for I/O and moves directly to the Check stage to run that callback. - Only after the first tick finishes does the OS wrap up the file read and queue the
fs.readFilecallback to the Poll stage. This callback then runs in the Poll stage of the second tick—after the Check stage of the first tick has already executedImmediate 1.
That's why you see Immediate 1 printed before File 1, even though Check comes after Poll in the event loop order. The I/O just wasn't ready in time for the first Poll stage pass.
内容的提问来源于stack exchange,提问作者Michael Samuel

