JavaScript distinguishes between microtasks and macrotasks primarily to effectively manage the timing and order of asynchronous operations. These two task types enable the JavaScript engine to maintain a fine-grained scheduling mechanism for controlling when and where asynchronous operations execute.
Macrotasks
Macrotasks are typically the browser's primary tasks, including but not limited to:
setTimeoutsetInterval- I/O operations
- UI rendering
- Event handling (e.g., click, scroll events)
The event loop executes one macrotask from the task queue whenever the execution stack is empty.
Microtasks
Microtasks are typically tasks requiring immediate response, executed after each macrotask and once the JavaScript execution environment is ready. Microtasks include:
Promisecallbacks (e.g.,.then,.catch, and.finally)MutationObservercallbacksqueueMicrotaskfunction
Execution Order
After each macrotask completes, before executing the next macrotask, the JavaScript engine processes all microtasks in the queue. This ensures microtasks execute between the end of the current macrotask and the start of the next macrotask, completing before new UI rendering. This guarantees quick asynchronous operation response while supporting high-priority tasks due to microtasks' minimal delay.
Why Distinguish
Key reasons for distinguishing include:
-
Performance Optimization: Microtasks allow JavaScript to quickly execute simple operations (e.g., resolving promises) without affecting UI rendering, enhancing application responsiveness and performance.
-
Control Asynchronous Operation Order: The distinction enables developers to manage asynchronous execution sequences. For instance, a
Promise-derived microtask resolves before the next UI rendering, whereassetTimeoutmay be deferred until the next macrotask. -
Avoid Blocking: For time-sensitive code, microtasks prevent blocking the macrotask queue, reducing the risk of long-running tasks stalling UI updates.
Example
Suppose we have the following code:
javascriptconsole.log('Macrotask starts'); setTimeout(() => { console.log('Macrotask'); }, 0); Promise.resolve().then(() => { console.log('Microtask'); }); console.log('Macrotask ends');
The execution order will be:
- Print 'Macrotask starts'
- When the current macrotask ends, schedule a
setTimeout - Print 'Macrotask ends'
- After the current macrotask ends, execute all microtasks in the queue
- Print 'Microtask'
- The microtask queue is empty; start the next macrotask
- Print 'Macrotask'
This example demonstrates that microtasks execute immediately after the execution stack clears, while macrotasks may be delayed by other queue tasks. This mechanism allows JavaScript to efficiently handle asynchronous events while maintaining precise control over execution order.