Often, when switching to JavaScript from any other language, developers face many frustrations. That’s because JavaScript has some unique features that may not be obvious or intuitive to those who have not used it before.

When we first start learning JavaScript, we are told it executes the code line-by-line.

console.log("Beginning");
console.log("Middle");
console.log("End");

This will write “Beginning”, “Middle”, “End” in the console in that order.

Here is an example of a common JavaScript interview question:
What will be the console output of running this JavaScript code?

console.log("Beginning");

setTimeout(() => {
  console.log("Middle");
}, 0);

console.log("End");

The output is not what most people learning JavaScript expect:
“Beginning”,  “End”,  “Middle”.

It is no surprise this result can confuse developers who have been told that JavaScript executes code line-by-line. Even the timeout is set to 0 milliseconds!
What is actually going on here?

Theoretical Model of JavaScript Event Loop

JavaScript is a single-threaded language, but it can handle multiple tasks simultaneously using Event Loop. Here is a visual representation of the Event Loop:

JavaScript Event Loop

Call Stack is a data structure that keeps track of function calls. When a function is called, a new frame (an execution context or stack frame) is created and pushed onto the call stack. This frame contains information about the function call, including the function’s arguments, local variables, and the return address. The function at the top of the call stack is the one currently being executed. When a function finishes its execution, its frame is popped off the stack, and the control returns to the previous frame on the stack.

Queue is a list of messages that are waiting to be executed. A message is a task to be executed. Queue holds various types of messages, such as DOM events, timer callbacks, network responses, and other asynchronous operations. Each message has an associated function that gets called to handle the message.

Event Loop is responsible for pushing messages from queue to stack. To do so, the message is removed from the queue, and its corresponding function is called with the message as an input parameter. Calling a function creates a new stack frame for that function’s use.

The Event Loop continuously checks the state of the call stack. If the call stack is empty, meaning no functions are being executed, the Event Loop takes the following message from the task queue and pushes it onto the call stack for execution.

Event Loop got its name from the way it is usually implemented:

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

So, what exactly happens in our example with setTimeout?

The first two arguments to the function setTimeout are a message to add to the queue and a time value (optional; defaults to 0). The time value represents the (minimum) delay, after which the message will be pushed into the queue. If there is no other message in the queue and the stack is empty, the message is processed right after the delay. However, if there are messages, the setTimeout message will have to wait for other messages to be processed. For this reason, the second argument indicates a minimum time — not a guaranteed time.

Internally, setTimeout sets up a timer using the browser’s or runtime environment’s underlying mechanisms. This timer operates separately from the JavaScript code execution and does not block the event loop or the message queue.

Once the specified delay has elapsed, the timer fires an event indicating that the delay has finished.

When the timer event fires, the callback function associated with setTimeout is added as a message to the queue.

The event loop will eventually pick up the message and execute the callback function when the call stack is empty.

When the code starts executing, messages for console.log(“Beginning”); and console.log(“End”); are added to the queue, and the setTimeout sets a timer using the browser or runtime environment. The timer runs out, and the callback of setTimeout gets pushed at the end of the queue. That is why it gets executed last.

Similar happens with other asynchronous code in JavaScript. The browser or execution environment handles the action without blocking the rest of the code, and when it is finished, it pushes the callback message to the queue if there is one.

This behavior is crucial for handling time-consuming tasks, network requests, and other operations without blocking the main thread and causing unresponsiveness in the user interface.

Let’s see another example of this – If we imagine a scenario where we want to notify a user that they have received a new message, but we do not want to interrupt them from using our app while we are fetching new notifications, then we could write something like this:

function fetchNotification() {
  fetch('https://example.com/messages)
    .then(response => {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return response.json();
    })
    .then(messagesData => {
      // Display number of messages without blocking the user
      const msgCountElement = document.querySelector(“#msgCount”);
      msgCountElement.textContent = messagesData.count
    })
}

The request is sent to the browser’s networking layer. The event loop continues executing other code while waiting for a response. Therefore, the user does not have to wait.


“JavaScript Event Loop” Tech Bite was brought to you by Ivana Zirojević, Junior Software Engineer at Atlantbh.

Tech Bites are tips, tricks, snippets or explanations about various programming technologies and paradigms, which can help engineers with their everyday job.

oban
Software DevelopmentTech Bites
February 23, 2024

Background Jobs in Elixir – Oban

When and why do we need background jobs? Nowadays, background job processing is indispensable in the world of web development. The need for background jobs stems from the fact that synchronous execution of time-consuming and resource-intensive tasks would heavily impact an application's  performance and user experience.  Even though Elixir is…
selenium
QA/Test AutomationTech Bites
December 22, 2023

Selenium Grid 4 with Docker

Introduction When talking about automation testing, one of the first things that comes to mind is Selenium. Selenium is a free, open-source automated testing framework used to validate web applications across different browsers and platforms. It is not just a single tool but a suite of software. Every component of…
redis
Software DevelopmentTech Bites
December 22, 2023

In-memory Caching using Redis

The importance of computer memory utilization The CPU and memory are the main components of any computer system. Computer memory stores data and program instructions, temporarily or permanently, that the CPU processes. In CPU-intensive applications with large amounts of data being processed, memory usually becomes the bottleneck, resulting in a…

Leave a Reply