The event loop is a mechanism in JavaScript that handles the execution of multiple chunks of your program over time. Each chunk is a piece of code that can be executed by the JavaScript engine, and these chunks can be scheduled to run in the future, which allows JavaScript to be asynchronous and non-blocking.
Let's break it down:
-
When your JavaScript program makes an API call, sets a timer, or responds to a user interaction, it's scheduling a chunk of code to be executed later.
Think of this like setting a reminder on your phone. When you make an API call (like asking a server for data), set a timer (like saying "remind me in 5 seconds"), or respond to a user interaction (like clicking a button), you're telling JavaScript "Hey, I want you to do this thing, but not right now. Do it later."
-
This chunk of code is added to a queue.
This is like adding your reminder to a list of reminders on your phone. JavaScript keeps a list (or "queue") of all the chunks of code that are scheduled to run later.
-
The event loop constantly checks this queue. When a chunk of code is ready to be executed (for example, when an API response is received or a timer finishes), the event loop executes it.
This is like your phone constantly checking your list of reminders. When it's time for a reminder (like when 5 seconds have passed for your timer, or when the server has responded to your API call), your phone alerts you. In the same way, when it's time for a chunk of code to run, the event loop takes it from the queue and executes it.
So, in simple terms, the event loop is like a personal assistant for JavaScript. It keeps track of all the tasks that JavaScript needs to do later, and when it's time to do a task, the event loop makes sure it gets done.
Stack
The JavaScript call stack is where the JavaScript engine keeps track of what function is currently being run (It is used for synchronous execution of code) and what functions are called from within that function, etc. When a function is called, it's added to the top of the stack. When a function finishes, it's removed from the stack. If the stack is full (a condition known as stack overflow), a program may crash. This process continues in a LIFO (Last In, First Out) manner, meaning the last function that gets pushed onto the stack is the first one to be popped out when its execution is complete.
function firstFunction() {
console.log("First function executed");
}
function secondFunction() {
firstFunction();
console.log("Second function executed");
}
secondFunction();
In this example, secondFunction
is called first, so it's added to the stack. Inside secondFunction
, firstFunction
is called, so it's added to the top of the stack. firstFunction
finishes and is removed from the stack, then secondFunction
finishes and is removed from the stack. All of this happens synchronously, meaning one after the other without any delay.
Microtasks
Microtasks are small units of work that are scheduled to be done as soon as possible, but not immediately. They are run after the current script has finished, but before control is returned back to the event loop, which manages the execution of tasks in JavaScript.
Think of it like this: you're doing your homework (the current script), and you remember you need to take out the trash (a microtask). You don't stop doing your homework right away to take out the trash. Instead, you finish your homework first, then take out the trash before starting a new task, like watching TV (the event loop picking up the next task).
Examples of microtasks include Promise callbacks and DOM mutations.
Promises in JavaScript represent a value that may not be available yet. When you create a Promise, you provide a function that will be called when the value is available (the Promise is resolved) or when something goes wrong (the Promise is rejected). This function is a microtask.
DOM mutations are changes to the structure of the web page. When you change the web page structure, the browser doesn't update the display right away. Instead, it waits until all the changes have been made, then updates the display in one go. This update is a microtask.
console.log('Start'); // This runs immediately
Promise.resolve().then(function() {
console.log('Microtask'); // This is a microtask
});
console.log('End Immediately'); // This runs immediately
In the code example, console.log('Start')
is run immediately. Then, a Promise is created with Promise.resolve()
, and a function is provided with then()
that will be called when the Promise is resolved. This function is a microtask. Finally, console.log('End Immediately')
is run.
Even though the Promise is resolved immediately with Promise.resolve()
, the function provided with then()
is not run immediately. Instead, it's scheduled as a microtask to be run as soon as possible, but not before the current script has finished. So, console.log('End Immediately')
is run before the microtask. The output of this code will be:
Start
End Immediately
Microtask
Macrotasks
Macrotasks are larger tasks that are scheduled to be run by the JavaScript engine. Each iteration of the event loop picks up one macrotask and executes it.
Think of it like this: you're watching TV (the event loop), and during each commercial break, you do one chore (a macrotask), like washing dishes or taking out the trash. You don't do all the chores at once, you do one during each break, then go back to watching TV.
Examples of macrotasks include setTimeout
, setInterval
, setImmediate
(Node.js), UI rendering, and I/O operations.
These are all tasks that are scheduled to be done in the future, not immediately. setTimeout
and setInterval
are timers that schedule a task to be done after a certain amount of time. setImmediate
is a Node.js function that schedules a task to be done as soon as possible, but not immediately. UI rendering is the task of updating the display of the web page. I/O operations are tasks that involve reading from or writing to a file or a network connection.
console.log('Start'); // This runs immediately
setTimeout(function() {
console.log('Macrotask'); // This is a macrotask
}, 0);
console.log('End Immediately'); // This runs immediately
In the code example, console.log('Start')
is run immediately. Then, a function is scheduled to be run after a delay of 0 milliseconds with setTimeout
. This function is a macrotask. Finally, console.log('End Immediately')
is run.
Even though the delay for setTimeout
is 0 milliseconds, the function is not run immediately. Instead, it's scheduled as a macrotask to be run as soon as possible, but not before the current script has finished and any microtasks have been processed. So, console.log('End Immediately')
is run before the macrotask. The output of this code will be:
Start
End Immediately
Macrotask
In this example, even though setTimeout
is essentially scheduling a task to be executed as soon as possible, it will not be executed before the Promise
callback (a microtask) from the previous example, because microtasks have higher priority and are processed immediately after the current script (and any other microtasks) finishes, but before any macrotasks are processed.
Kyle Simpson, in his book "You Don't Know JS: Async & Performance", explains the event loop as follows:
"The event loop is this constantly running process in the JS engine, which checks if there's a function in the queue to be executed, and if so, executes it. It's a loop that keeps running as long as there are any functions left to be executed."
In summary, the event loop allows JavaScript to be asynchronous and non-blocking by executing chunks of code at a time, and scheduling other chunks to be run in the future.
You can play visually with the event loop in Javascript Visualizer 9000.