Event Loop and MacroTasks/MicroTasks in JavaScript
Event Loop and MacroTasks/MicroTasks in JavaScript
Description
The Event Loop is the core mechanism enabling asynchronous programming in JavaScript, responsible for coordinating work between the Call Stack, Web APIs, and Task Queues. MacroTasks and MicroTasks are two types of tasks in the task queue, and their execution priority and timing directly affect the order of asynchronous code. Understanding the difference between them helps avoid pitfalls related to execution order in real-world development.
Solution Process
-
Basic Concepts
- Call Stack: The place where synchronous code executes in order. Functions are pushed onto the stack when called and popped off after execution.
- Web APIs: Asynchronous functionalities provided by the browser (e.g.,
setTimeout,AJAX), handled by other threads. Upon completion, their callback functions are pushed into the task queue. - Event Loop: Continuously checks if the call stack is empty. If it is, it takes the first task from the task queue and pushes it onto the call stack for execution.
-
Definition of MacroTasks and MicroTasks
- MacroTasks: Include the entire script,
setTimeout,setInterval, I/O operations, UI rendering, etc. - MicroTasks: Include
Promise.then/catch/finally,MutationObserver,queueMicrotask, etc. - Key Rules:
- After executing one MacroTask, the Event Loop will clear all tasks in the current MicroTask queue.
- MicroTasks have higher priority than MacroTasks.
- MacroTasks: Include the entire script,
-
Execution Flow Example Analysis
Step-by-step demonstration of execution order with code:console.log("1"); // Synchronous code, outputs immediately setTimeout(() => { console.log("2"); // MacroTask, enters MacroTask queue }, 0); Promise.resolve().then(() => { console.log("3"); // MicroTask, enters MicroTask queue }); console.log("4"); // Synchronous code, outputs immediatelyStep Breakdown:
- Synchronous Phase:
- Output
1→console.log("1")executes and clears from the call stack. - Encounter
setTimeout, hand its callback to Web API for timing (to be placed in MacroTask queue after 0ms). - Encounter
Promise.resolve().then, place its callback in the MicroTask queue. - Output
4→ call stack is cleared.
- Output
- MicroTask Phase:
Once the call stack is empty, the Event Loop first checks the MicroTask queue, finds thePromisecallback, and outputs3. - MacroTask Phase:
After clearing the MicroTask queue, it takes thesetTimeoutcallback from the MacroTask queue and outputs2.
Final Output Order:1 → 4 → 3 → 2.
- Synchronous Phase:
-
Complex Case with Nested Scenarios
setTimeout(() => console.log("MacroTask1"), 0); Promise.resolve().then(() => { console.log("MicroTask1"); Promise.resolve().then(() => console.log("MicroTask2")); }); queueMicrotask(() => console.log("MicroTask3"));Execution Order:
- Synchronous code execution: Register MacroTask1 to its queue, register
Promise.then(MicroTask1) to the MicroTask queue, registerqueueMicrotask(MicroTask3) to the MicroTask queue. - Clear MicroTask queue: Execute MicroTask1 in registration order → after output, the nested
Promise.then(MicroTask2) is added to the current MicroTask queue, continue clearing the MicroTask queue (MicroTask3 → MicroTask2). - Finally execute MacroTask1.
Output Order:MicroTask1 → MicroTask3 → MicroTask2 → MacroTask1.
- Synchronous code execution: Register MacroTask1 to its queue, register
-
Summary and Key Points
- The MicroTask queue is cleared after each MacroTask execution, and any new MicroTasks generated during this process are added to and executed from the current queue (recursive clearing).
- MacroTasks execute in rounds of the Event Loop. For example, the delay in
setTimeoutis the minimum time relative to the completion of the current MacroTask. - Common Misconception:
setTimeout(fn, 0)does not execute immediately; it waits for the current synchronous code and the MicroTask queue to be cleared before execution.