What is microtask vs macrotask?
In JavaScript, especially in a browser or Node.js environment, code execution is managed by the Event Loop. This single-threaded model relies on a queue-based system to handle asynchronous operations. Within this system, tasks are categorized into two main types: macrotasks and microtasks, each with distinct characteristics and execution priorities.
The JavaScript Event Loop
JavaScript environments, like web browsers and Node.js, use an 'Event Loop' to handle asynchronous operations and manage execution order. Since JavaScript is single-threaded, the Event Loop ensures non-blocking behavior by processing tasks in a specific sequence, preventing long-running operations from freezing the application. When the call stack is empty, the Event Loop checks task queues for work.
Macrotasks (Tasks)
Macrotasks, often simply referred to as 'tasks,' represent a discrete unit of work that the browser or Node.js runtime schedules. Each macrotask is processed one at a time per loop iteration. After a macrotask completes, the Event Loop checks the microtask queue before moving on to the next macrotask. Macrotasks are typically associated with I/O, timers, or UI rendering.
- setTimeout()
- setInterval()
- setImmediate() (Node.js specific)
- requestAnimationFrame() (browser specific, often considered a separate queue but behaves similarly in terms of loop progression)
- I/O operations (e.g., network requests, file reading)
- UI rendering events
- MessageChannel.postMessage()
Microtasks (Jobs)
Microtasks, also known as 'jobs,' are smaller, more urgent tasks that are executed *after* the currently executing script or macrotask finishes, but *before* the Event Loop proceeds to the next macrotask. This means that all microtasks within the microtask queue are drained and executed completely before the browser's rendering engine or the Event Loop can pick up another macrotask. Microtasks are typically used for more immediate, lightweight asynchronous operations.
- Promise callbacks (.then(), .catch(), .finally())
- async/await (which uses Promises internally)
- MutationObserver callbacks
- queueMicrotask()
- process.nextTick() (Node.js specific, has higher priority than Promise microtasks within the Node.js event loop phase)
Execution Order and Priority
The crucial difference lies in their execution priority within a single tick of the Event Loop. When the Event Loop runs: 1. It picks one macrotask from the macrotask queue and executes it. 2. After that macrotask completes, it immediately drains and executes *all* available microtasks in the microtask queue. 3. Only after the microtask queue is empty does the Event Loop consider picking up the next macrotask. This gives microtasks higher priority for execution within a given loop iteration.
console.log('Script start');
setTimeout(() => {
console.log('Macrotask: setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Microtask: Promise.then 1');
}).then(() => {
console.log('Microtask: Promise.then 2');
});
console.log('Script end');
// Expected output:
// Script start
// Script end
// Microtask: Promise.then 1
// Microtask: Promise.then 2
// Macrotask: setTimeout
Why Does It Matter?
Understanding the distinction between microtasks and macrotasks is vital for predicting the order of asynchronous operations, especially when dealing with Promises, UI updates, and performance optimization. Misunderstanding this can lead to subtle bugs related to state consistency, UI responsiveness, or data races. Microtasks are often preferred for tasks that need to happen immediately after the current script but before any potential UI rendering or further I/O, ensuring a more predictable state.
Comparison Table
| Feature | Macrotask | Microtask |
|---|---|---|
| Scheduling | Browser/Node.js APIs (e.g., setTimeout, I/O) | JavaScript engine (Promise, MutationObserver, queueMicrotask) |
| Execution | One per Event Loop iteration (after all microtasks) | All in queue, after current script/macrotask, before next macrotask |
| Priority | Lower (one per tick) | Higher (all before next macrotask) |
| Examples | setTimeout, setInterval, I/O, UI events, setImmediate | Promise callbacks, async/await, MutationObserver, queueMicrotask, process.nextTick |
| Impact | Can allow UI re-renders or other I/O between tasks | Does not allow UI re-renders or other macrotasks between its tasks within the same cycle |