Explanation
What just happened
One thread runs all the JavaScript, and nothing interrupts the running script. setTimeout(…, 0) queued its callback as a task; Promise.then queued its callback as a microtask. When the script ended, the event loop first ran every microtask (promise), then took the next task (timeout).
Try the what-if switch
- await:
load()runs until itsawait, then returns to the script. The rest ofloadis a microtask, so it runs after the script but before the timeout. - endless microtasks: each microtask queues another, a million times. The 0 ms timeout waits until the microtask queue is finally empty.
The rule
Run the current task to the end. Then run all microtasks, including any they queue. Then take one task. Repeat.
Watch out
“0 ms” is a minimum, not a promise. A timer callback always waits for the script and every microtask in front of it.
Common questions
Why does a promise run before setTimeout 0?
Promise callbacks are microtasks; timer callbacks are tasks. After the current script, the event loop runs every queued microtask before it takes the next task, so the promise wins even though the timer was set first.
Is the code after await a microtask?
Yes. An async function runs until its first await, then returns. The rest is queued as a microtask, so it runs right after the current script, before any waiting timer.
Can microtasks block timers?
Yes. If each microtask queues another, the queue never empties and timers wait. In our test a 0 ms timeout waited for a million microtasks.
Verified: this timeline matches a real program, checked in CI on Node.js 22 and 24. How we verify. Unclear or wrong? Tell us