How does a "promise microtask" work?
What a microtask is
A microtask is a small task that runs after the current call stack,
but before the engine moves on to macrotasks (for example setTimeout, setInterval, fetch, etc.).
Simply put: Microtasks are a "queue of urgent tasks" that run right after the current code, before the "next tick" of the Event Loop begins.
Promises create microtasks
When a promise moves to the fulfilled or rejected state,
its .then() / .catch() / .finally() handlers
do not run immediately,
but are placed into the microtask queue.
Example
console.log('1');
Promise.resolve().then(() => console.log('2'));
console.log('3');Result:
1
3
2Why:
- First, all the synchronous code runs (on the call stack).
- Then the Event Loop sees the stack is empty
and runs all the microtasks (in this case,
.then()).
Where microtasks are created
Microtasks are created:
- by
.then(),.catch(),.finally()on aPromise, - by
queueMicrotask(fn), - by
MutationObserver(in the browser), - by
process.nextTick()in Node.js.
The microtask and macrotask queues
The JS engine processes events like this:
- Runs all the synchronous code (Call Stack).
- Runs all the microtasks, until the queue is empty.
- Moves on to one macrotask (for example,
setTimeout). - Runs all the microtasks that accumulated after it, again.
- And so on in a loop.
Example: comparison with macrotasks
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');Output:
A
D
C
BWhy:
A,Dare synchronous;Cis a microtask (from the promise);Bis a macrotask (fromsetTimeout).
The microtask (C) runs before the timer (B).
Example with several microtasks
Promise.resolve().then(() => console.log('1'));
Promise.resolve().then(() => console.log('2'));
Promise.resolve().then(() => console.log('3'));
console.log('4');Output:
4
1
2
3All the .then() calls land in the same microtask queue,
and run in the order they were added,
after the synchronous code finishes (4).
Example: a microtask inside a microtask
Promise.resolve().then(() => {
console.log('A');
Promise.resolve().then(() => console.log('B'));
});Output:
A
BWhy:
- After
A, a new microtask (B) runs, added while the previous one was running.
An important rule
After each macrotask finishes, the engine always fully runs all the microtasks, before picking up the next macrotask.
A simple analogy
Imagine the Event Loop is a queue at a cafe.
- "Macrotasks" are regular orders (large ones).
- "Microtasks" are handing back change quickly and settling up.
The barista takes an order (synchronous code), then hands out all the change (microtasks), and only then moves on to the next customer (a new macrotask).
Summary
| Task type | Examples | When it runs |
|---|---|---|
| Microtasks | .then(), .catch(), .finally(), queueMicrotask() | After the current code, before the next macrotask |
| Macrotasks | setTimeout, setInterval, fetch, DOM events | After all the microtasks of the current tick |
Promises always use microtasks, which is why their callbacks run before timers.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.