JavaScript event loop is crucial for writing performant and predictable code. A key part of the event loop involves two queues: the macrotask queue and the microtask queue. While both are responsible for handling asynchronous tasks, they differ significantly in their execution order and priority.
Macrotask Queue
The macrotask queue holds tasks that are often triggered by external events or browser APIs. Each iteration of the event loop processes one macrotask from this queue.
Examples of macrotasks include:
- setTimeout() and setInterval() callbacks
- I/O operations (e.g., network requests, file access)
- User interface rendering
- Script execution (the initial script execution itself is a macrotask)
- postMessage
Think of the macrotask queue as the general queue for larger, more discrete tasks. The browser gets to take a break (render the UI, handle user input) between each macrotask.
Microtask Queue
The microtask queue, on the other hand, has a higher priority. After each macrotask completes, the event loop processes all available microtasks before moving on to the next macrotask.
Examples of microtasks include:
- Promises (
.then(),.catch(),.finally()callbacks) - MutationObserver callbacks
- process.nextTick() (Node.js specific)
Microtasks are intended for tasks that need to be executed immediately after the current macrotask, before the browser has a chance to update the UI or handle other events. They offer a way to perform fine-grained updates and avoid unnecessary re-renders.
Key Differences Summarized
- Priority: Microtasks have higher priority than macrotasks. All microtasks in the microtask queue are executed before the next macrotask is processed.
- Quantity: After a macrotask completes, the event loop processes *all* microtasks in the microtask queue before moving to the next macrotask. It only processes *one* macrotask per event loop iteration.
- Use Cases: Macrotasks are generally used for larger, discrete asynchronous operations. Microtasks are used for smaller, more immediate tasks that should be executed before the browser updates the UI.
Example
Consider the following code snippet:
console.log('Script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise resolved');
});
console.log('Script end');
The output will be:
Script start
Script end
Promise resolved
setTimeout
Explanation:
- 'Script start' is logged immediately.
- The
setTimeoutcallback is added to the macrotask queue. - The
Promise.resolve().then()callback is added to the microtask queue. - 'Script end' is logged immediately.
- The current macrotask (initial script execution) finishes.
- The event loop checks the microtask queue. The Promise callback is executed, logging 'Promise resolved'.
- The event loop checks the macrotask queue. The
setTimeoutcallback is executed, logging 'setTimeout'.
Conclusion
Understanding the difference between macrotasks and microtasks is essential for managing asynchronous operations in JavaScript. By recognizing their priorities and execution order, you can write more predictable, performant, and responsive applications.