The JavaScript event loop is a fundamental part of how JavaScript handles asynchronous code execution. It allows JavaScript to perform non-blocking I/O operations despite being single-threaded, by managing the execution of asynchronous code. Understanding the event loop is crucial for mastering JavaScript, especially when dealing with tasks like handling events, promises, and setTimeout calls.
A key concept in the event loop is the distinction between macrotasks and microtasks, which are processed in a specific order.
The Event Loop
At its core, the event loop continuously checks the call stack and task queues to decide which task to execute next.
- Call Stack: The stack where JavaScript functions are executed.
- Task Queues: There are two main types of task queues:
- Macrotask Queue (also called Task Queue): Includes tasks like
setTimeout
,setInterval
, and I/O events (such as click events). - Microtask Queue: Includes tasks like promises (
then
,catch
,finally
), andMutationObserver
callbacks.
- Macrotask Queue (also called Task Queue): Includes tasks like
How the Event Loop Works:
- The event loop first checks if there are any microtasks to execute.
- If there are, it will process all microtasks in the microtask queue before it processes any macrotasks.
- Once the microtask queue is empty, the event loop will take the next task from the macrotask queue and run it.
- This cycle repeats indefinitely.
Macrotasks
Macrotasks are larger, higher-level tasks that are executed by the event loop. These include tasks like rendering updates, handling events, or invoking functions passed to setTimeout
, setInterval
, or the requestAnimationFrame
function.
Common Macrotasks:
setTimeout()
setInterval()
- Event handlers (clicks, keypresses)
- I/O tasks (like file reads)
requestAnimationFrame()
Macrotask Execution:
- When a macrotask is scheduled (e.g., through
setTimeout
), it goes into the macrotask queue. - The event loop picks up these tasks one at a time after all microtasks have been executed.
Example of Macrotask (setTimeout
):
Output:
In this example:
- The first two
console.log
calls are executed synchronously. - The
setTimeout
call is a macrotask, so it's added to the macrotask queue and executed after the synchronous code finishes. - Even though the delay is 0 milliseconds, it still goes to the macrotask queue, meaning it will only be executed after the current call stack is cleared and any microtasks are processed.
Microtasks
Microtasks are smaller tasks that should be executed as soon as possible, before any macrotasks. They are used to handle promises and other small tasks that need to run after the current task but before any rendering or other events are handled.
Common Microtasks:
Promise.then()
,Promise.catch()
,Promise.finally()
MutationObserver
callbacks
Microtask Execution:
- When a microtask is scheduled (e.g., by resolving a promise), it goes into the microtask queue.
- The event loop will process all microtasks in the microtask queue before moving to any macrotasks.
Example of Microtask (Promise):
Output:
In this example:
- The
console.log("Start")
andconsole.log("End")
are executed synchronously. - The promise resolution (
.then
) is a microtask, and it is executed immediately after the synchronous code but before any macrotasks, even though the promise was created asynchronously.
Event Loop with Macrotasks and Microtasks
Let's combine macrotasks and microtasks to see how they work in practice:
Example of Event Loop with Macrotasks and Microtasks:
Output:
Explanation of the Output:
- The synchronous
console.log("Start")
andconsole.log("End")
are executed first. - The first
setTimeout
(macrotask 1) is scheduled to run after 0 milliseconds and goes to the macrotask queue. - The second
setTimeout
(macrotask 2) is scheduled in the same way, but it also goes to the macrotask queue. - The first
Promise.resolve().then()
microtask (microtask 2) is added to the microtask queue and executed right after the synchronous code finishes. - After microtasks are finished, macrotask 1 is processed. Within this macrotask, a new microtask (
Microtask 1
) is scheduled, which is executed before macrotask 2. - Finally, macrotask 2 is executed.
Key Differences Between Macrotasks and Microtasks:
Feature | Macrotasks | Microtasks |
---|---|---|
Queue | Macrotask Queue | Microtask Queue |
Types | setTimeout() , setInterval() , events (click, keypress), requestAnimationFrame() | Promise.then() , Promise.catch() , MutationObserver |
Execution Order | Executed after all microtasks in the queue | Executed after the current task, before macrotasks |
Priority | Lower priority (after all microtasks) | Higher priority (before macrotasks) |
Conclusion
- Macrotasks are high-level tasks that are processed in the macrotask queue after all the microtasks are executed.
- Microtasks are lower-level tasks that need to be executed as soon as possible after the current execution context finishes. They are processed in the microtask queue before any macrotasks.
- The event loop ensures that all microtasks are processed before moving on to any macrotasks, even if they were scheduled later.
Understanding this distinction and how tasks are processed by the event loop is key to mastering asynchronous programming in JavaScript, particularly when dealing with promises, events, and user interactions.