Microtasks
Microtasks are high-priority queued callbacks that run after the current call stack finishes and before the next macrotask.
The most common microtasks you will see come from promises.
console.log("A");
Promise.resolve().then(() => {
console.log("B");
});
console.log("C");Output:
A
C
BThe .then() callback is a microtask.
When Microtasks Run
The simplified order is:
- Run synchronous code on the call stack.
- Run all queued microtasks.
- Run the next macrotask.
- Repeat.
Example:
console.log("Start");
setTimeout(() => {
console.log("Timer");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");Output:
Start
End
Promise
TimerThe promise microtask runs before the timer task.
Common Microtask Sources
Common microtask sources include:
- Promise
.then() - Promise
.catch() - Promise
.finally() queueMicrotask()- mutation observer callbacks in browsers
Most beginner examples use promises.
queueMicrotask()
queueMicrotask() schedules a microtask directly.
console.log("A");
queueMicrotask(() => {
console.log("B");
});
console.log("C");Output:
A
C
BYou will not need queueMicrotask() often as a beginner, but it helps reveal how microtasks work.
Microtasks Drain Fully
When JavaScript starts processing microtasks, it runs all currently queued microtasks before moving to the next macrotask.
Promise.resolve().then(() => {
console.log("Microtask 1");
Promise.resolve().then(() => {
console.log("Microtask 2");
});
});
setTimeout(() => {
console.log("Timer");
}, 0);Output:
Microtask 1
Microtask 2
TimerThe second microtask is added while microtasks are being processed.
It still runs before the timer.
Microtask Starvation
If microtasks keep adding more microtasks forever, timers and rendering may not get a chance to run.
function loop() {
queueMicrotask(loop);
}
loop();This is a bad idea.
It can starve the event loop.
Use microtasks carefully.
Promises and Microtasks
Even an already-resolved Promise schedules .then() for later.
const promise = Promise.resolve("Done");
promise.then((value) => {
console.log(value);
});
console.log("After");Output:
After
Done.then() does not run immediately.
It runs as a microtask after the current stack clears.
Best Practices
Remember that Promise callbacks are microtasks.
Expect microtasks to run before timers.
Avoid creating endless microtask loops.
Use microtasks mainly through promises unless you have a specific reason.
When predicting output, run synchronous code first, then microtasks.
Common Mistakes
Mistake 1: Expecting .then() to Run Immediately
Promise.resolve().then(() => console.log("Promise"));
console.log("Sync");This logs:
Sync
PromiseMistake 2: Expecting Timers Before Promises
setTimeout(() => console.log("Timer"), 0);
Promise.resolve().then(() => console.log("Promise"));The promise callback runs first after synchronous code.
Mistake 3: Scheduling Infinite Microtasks
Endless microtask loops can block timers and rendering.
Summary
Microtasks run after the call stack clears and before the next macrotask.
- Promise callbacks are microtasks.
queueMicrotask()schedules a microtask directly.- Microtasks run before timers.
- The microtask queue drains fully before the next macrotask.
- Too many microtasks can starve timers and rendering.