Promises before timers
Let's go back to the kitchen. The cook has two piles of slips. The first holds new orders, and he takes them one at a time. The second holds small finishing touches for the dish he has finished: salt the soup, add a fork. After each job, the cook first clears all the finishing touches and only then takes the next order.
A timer callback and a then callback also both wait for the current code to finish. When both are waiting, the promise callback runs first, even if the timer was set earlier. Let's see:
console.log("start");
setTimeout(() => console.log("timeout"), 0);
Promise.resolve(1)
.then((v) => {
console.log("then", v);
return v + 1;
})
.then((v) => console.log("then", v));
console.log("end");
// start
// end
// then 1
// then 2
// timeoutPromise.resolve(1) creates a promise that is already fulfilled with the value 1. Both plain lines print first, then both steps of the chain, and only then the timer callback with a delay of 0.
Two queues
Ready callbacks wait in two queues, not one:
thenandcatchcallbacks go into the microtask queue: these are the cook's finishing touches;setTimeoutcallbacks go into the macrotask queue: these are new orders.
The specification calls macrotasks tasks, so articles use both words.
A callback enters the microtask queue only once its promise is fulfilled or rejected; while the promise is pending, the callback waits aside. That is why a then callback on a promise that a timer fulfills does not get ahead of that timer.
The event loop's rule
The event loop follows one rule. Once the current code has finished, the loop runs all microtasks and only then takes one macrotask. A microtask added while the loop was running microtasks also runs right away, before the next macrotask.
In the example, it goes like this:
- While the current code runs,
startandendprint. The timer is set, and the firstthencallback already stands in the microtask queue. - The loop runs the first
then: it printsthen 1and returns2. - That puts the second
theninto the microtask queue, and it runs too. - No microtasks are left, and only now does the loop take the timer callback.
Step through this program in the widget. Watch when the call stack empties and which callback the loop takes next.
1console.log("start");2setTimeout(() => console.log("timeout"), 0);3 4Promise.resolve(1)5 .then((v) => {6 console.log("then", v);7 return v + 1;8 })9 .then((v) => console.log("then", v));10console.log("end");
The current code prints start.
After each macrotask, the loop again runs all microtasks and then takes the next macrotask. The timer's delay does not matter here: even 0 does not get ahead of a then callback that is already waiting.
Here the cook analogy stops working. A tired cook may put the finishing touches aside and take a new order, but the loop never does. If microtasks keep adding new microtasks without end, the timer never gets its turn.
This is a classic interview question: what does code print that has console.log, setTimeout(…, 0) and Promise.resolve().then(…)? The answer follows the rule: all the current code first, then all microtasks, then the timer's macrotask.
An already fulfilled promise
A then callback never runs on the spot, even for a promise that is already fulfilled. Calling then on such a promise only queues the callback, and the current code moves on:
const p = Promise.resolve("ready");
p.then((v) => console.log(v));
console.log("sync");
// sync
// readyp has its value from the first line, yet ready prints second. The order is always the same: first all the current code, then the microtasks, then the timer macrotasks.
Key points
thenandcatchcallbacks wait in the microtask queue,setTimeoutcallbacks in the macrotask queue.- A callback enters the microtask queue only once its promise is fulfilled or rejected.
- Once the current code has finished, the loop runs all microtasks and only then one macrotask.
- Even for a fulfilled promise, a
thencallback waits for the current code to end.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.