JavaScript is a single-threaded, synchronous language: This means it processes one task at a time on a single execution thread. Usually, that’s no big deal, but now imagine we’re running a task that takes 30 seconds, Oops… We will wait for 30 seconds during that task before anything happens, as JavaScript, by default, runs on the browser’s main thread, causing the entire UI to become stuck. This is terrible.
However, with the rise of web applications demanding responsiveness and scalability, JavaScript has adopted a powerful mechanism for asynchronous programming: the event loop. This will help javascript solve the above problem. So, what exactly is the event loop and how does it work? Let’s explore it together in this article.
What is Event Loop?
Event Loop is a mechanism for handling asynchronous tasks in JavaScript. It uses an endless loop to check and manage tasks. Even though JavaScript is single-threaded, it handles multiple tasks simultaneously by performing only one task at a time.
Why is Event Loop important?
- Avoid application crashes: Handle asynchronous tasks without blocking the main thread.
- Asynchronous processing: Handle asynchronous tasks such as API calls or database operations without slowing down the program.
- Safe when executing asynchronous tasks: Event Loop manages and coordinates the execution of tasks without conflicts due to resource sharing in Javascript.
- Support Promises and async/await: Instead of having to use many nested callbacks, just use async/await to make the code simpler and easier to manage.
- Control the execution process: A deep understanding of Event Loop, helps developers predict the execution flow, and debug effectively and quickly.
- User interface: Maintain user experience when there are heavy tasks.
- Improve performance: Javascript can handle many tasks more effectively without having to create many threads, saving system resources.
How Event Loop works in JS
The above schema is an event loop workflow. The call stack executes synchronous code, such as function calls. When the code encounters an asynchronous operation, like a setTimeout
or a fetch
request, the browser’s Web API processes the operation.
After completing the operation, the Web API places the callback in the task queue (for macro tasks) or the microtask queue (for promises).
The event loop checks if the call stack is empty. If it is:
- The system processes all tasks in the microtask queue first, following the order in which they were added.
- Then, it processes the next task from the task queue.
The process continues, repeating the event loop until there are no more tasks to process.
To better understand the components in the event loop operation process, as well as the role and function of each component, we will learn more details in the following sections.
Call stack
- Call stack is an important component in JS, managing the execution of program tasks.
- JavaScript adds the called function to the call stack.
- Call stack works according to the FILO (First In Last Out) mechanism.
- The function executes and then pushes itself off the call stack.
JavaScript can only process one task at a time. This means that longer-running tasks will likely block any other tasks executing behind them, effectively freezing the program!
The above example shows that longRunningTask() needs to be pushed off the Call Stack, which takes some time, before the importantTask() function can execute.
In real life, when building a program, there will be many tasks that run for longer. Will this cause the entire application to hang? Luckily, the answer is no! Browsers support WebAPIs
Web APIs
Web API provides a set of interactive interfaces that include features like fetch, setTimeout, and a few others.
Web APIs allow for asynchronous operations and offload longer-running tasks to the browser. Calling an API method is essentially just passing the longer-running task to the browser environment and setting it to handle it when it completes.
The program quickly pushes the async task off the Call Stack after starting it without waiting for the result. Web APIs typically use two approaches: callback-based or promise-based.
Do you wonder where the async task will be placed for execution? We will continue to discover the next component to find out.
Task Queue
Instead, the system adds the async task to the Task Queue (also known as the Callback Queue), which contains Web API callbacks and events waiting to be executed at some point in the future.
Now, the async task is in the Task Queue… But when will it be executed?
Event Loop
This is the responsibility of the Event Loop, which is to continuously check if the call stack is empty. When the call stack becomes empty – meaning no tasks are being executed – it picks the first task from the task queue and moves it to the call stack, where the callback executes.
Whenever you call set Timeout
, the call stack executes it, handling only the initialization of the timer, while the browser tracks the timer. Once the timer expires, the task queue receives the timer’s callback. If the call stack is busy processing other tasks, the callback will have to wait in the Task queue.
Now you know how to handle callback-based APIs. However, most Web APIs use promise-based methods. So, will you handle these web APIs here or somewhere else? The answer is Microtask Queue which is the next component we will explore.
Microtask Queue
In addition to the regular event queue, JavaScript has a special queue called the microtask queue. The system processes tasks in this queue after the currently executing script but before any events from the event queue.
Microtask Queue is another queue in the runtime with higher priority than Task Queue. This queue serves the purpose of:
- Promise handler callbacks (then(callback), catch(callback) and finally(callback))
- async/await
- MutationObserver callbacks
- queueMicrotask callbacks
When the Call Stack is empty, the Event Loop will prioritize tasks in the Microtask Queue before the Task Queue.
You might wonder, after reading this far: ‘Are all Web APIs processed asynchronously?’ No! Some methods, such as localStorage.setItem() and document.getElementById(), still process synchronously.
Conclusion
The JavaScript event loop is a vital concept that allows developers to write efficient, non-blocking asynchronous code. By understanding how the event loop works, you can avoid performance pitfalls, ensure responsiveness, and master complex asynchronous patterns. Through this article you will:
- Understand asynchronous and non-blocking JavaScript, and how the Event Loop, Task Queue, and Microtask Queue work together.
- The Event Loop prioritizes the Microtask Queue to process promises and related tasks quickly, then executes tasks in the Task Queue based on their order.
- This way of working allows JavaScript to handle complex asynchronous operations in a single-threaded environment.
Would you like to read more articles by Tekos’s Team? Everything’s here.