Skip to content
TaeyoungKim.dev

Why setTimeout(0) Runs After a Promise Callback in JavaScript

WebWritten 3 min readTaeyoungKim
LinkedInX

setTimeout(fn, 0) looks as if it should run “right now,” yet the next console line appears first. The timer is not broken. 0 is a delay used when scheduling the callback for a later task, not an instruction to interrupt the code currently running.

Why does the next line run before setTimeout(0)?

Run this example as written:

js
console.log('A');

setTimeout(() => console.log('D'), 0);

Promise.resolve().then(() => console.log('C'));

console.log('B');
text
A
B
C
D

A and B belong to the current synchronous job. setTimeout registers callback D and returns, allowing B to print first. Changing 0 to 1 or 10 does not allow D to interrupt the current code.

The current code prints A and B. The Promise callback runs as a microtask, followed by the timer callback in a later task. A zero timer delay does not mean immediate execution within the current job.

Why does Promise.then run before the timer?

Here, Promise.resolve() is already fulfilled, but its .then() callback C still does not run at the call site. After the current code reaches B, the queued microtask runs before the timer's task, so C precedes D.

PhaseWorkConsole
Current codePrint, schedule timer, schedule Promise callback, printA, B
Microtask.then() on the fulfilled PromiseC
Later timer tasksetTimeout callbackD

Calling both callbacks “asynchronous” is too coarse to explain this order. Trace current code → microtask → timer task for this example. The MDN JavaScript execution model provides more detail on tasks and microtasks.

Is 0 ms a signal that earlier work is complete?

No. A zero-delay timer only schedules its callback; it does not know whether an earlier save or network request finished. Code that assumes “the save will be done on the next turn” can fail intermittently. If the work returns a Promise that represents completion, wait for that Promise.

js
async function saveAndNotify(save) {
  await save();
  console.log('Save complete');
}

This message is justified only if save() returns a Promise that actually represents completion. If save() starts a separate request and returns nothing, await has no completion signal to wait for. Check the called function's contract.

Zero milliseconds is not an exact execution time either; busy work or host scheduling may delay the callback. A → B → C → D describes this small example, not a universal ordering guarantee for every asynchronous API or elapsed time.

Key takeaways

setTimeout(fn, 0) schedules a timer callback rather than running it immediately. In this example, synchronous A and B finish, then Promise microtask C, then timer task D. To check whether a job finished, await the Promise that represents it rather than using a timer.

Author

TaeyoungKim

Connecting technical foundations with implementation, verification, and production decisions.

#JavaScript#setTimeout#Promise#event loop#execution order

Read next