Demystifying Event Loop in JavaScript

Demystifying Event Loop in JavaScript

JavaScript Concurrency model

JavaScript is a programming language that executes codes synchronously, and that's why it is called a single-threaded non-blocking programming language which means that it will be able to do only one thing at a time.

The Event Loop is what brings concurrency to JavaScript, and it is also the secret behind JavaScript's asynchronous programming as it gives us the illusion of multi-threading, meaning that it will be able to do multiple things at a time. Generally, an event loop in computer programming is a programming structure that continually tests for external events and calls the appropriate routines to handle them. An event loop is often the main loop in a program that typically waits for the user to trigger something.

In this article, we will learn and have an in-depth understanding of how event loop and concurrency work in JavaScript. Some code snippets and snapshots have been attached to the appropriate sections for practical demonstration and pictorial explanation respectively.

Keywords

These are the keywords to look out for in this article:

  • Concurrency

  • Call Stack

  • Task Queue

  • Web APIs

  • Synchronous

  • Asynchronous

  • Callback Queues

  • Single-Threaded

  • Multi-Threaded

  • Memory allocation

  • V8 Engine

How Event Loop works in JavaScript

An event loop in JavaScript looks at the call stack and the task queue. If the call stack is empty, it then takes the first thing on the task queue and pushes it onto the call stack, which effectively runs it

Let's take a critical look at this block of code:

(() => {

  console.log('this is the start point');

  setTimeout(() => {
    console.log('Callback 1: this is a message from call back');
  }); // has a default time value of 0

  console.log('this is just a message');

  setTimeout(() => {
    console.log('Callback 2: this is a message from call back');
  }, 0); // has a time value of 0

 setTimeout(() => {
    console.log('Callback 3: this is a message from call back');
  }, 1000); // has a time value of 1 second

  console.log('this is the end point');

})();

And its output:

 "This is the start"
 "This is just a message"
 "This is the end"
 "Callback 1: This is a message from call back"
 "Callback 2: This is a message from call back"
 "Callback 3: This is a message from call back"

As you can see, Zero delays in setTimeout(), and setInterval() do not mean the call back will be invoked after zero milliseconds. Calling setTimeout and setInterval with a delay of 0 (zero) milliseconds do not execute the callback function after the given interval.

The execution depends on the number of waiting tasks in the queue. In the example above, the message "this is just a message" will be written to the console before the message in the callback gets processed because the delay is the minimum time required for the runtime to process the request (not a guaranteed time).

The setTimeout needs to wait for all the code for queued messages to complete, even though you specified a particular time limit for your setTimeout.

Event Loop Workflow

Events, timers, and Ajax requests are all provided on the client-side (Frontend) by the browsers and are often referred to as Web API. They are the ones that allow single-threaded JavaScript to be non-blocking, concurrent, and asynchronous!

There are three major aspects of the execution workflow of any JavaScript program, Call stack, web API, and the Task queue or Callback queue.

The Call Stack

A stack is a data structure in which the last element added is always the first to be removed from the stack. You could think of it as a stack of a plate in which only the first plate, which was the last added, can be removed first. A Call Stack is simply a stack data structure where tasks or code is executed accordingly.

Let's take a look at this example:

When you call the function printSquare(), it is pushed onto the call stack. It then calls the printSquare() function and the printSquare() function is pushed onto the stack and also calls the multiply() function which is also pushed onto the stack. Since the multiply function returns and is the last thing that was pushed to the stack, it gets resolved first and is removed from the stack, followed by the square() function and then the printSquare() function.

The Web API

This is where code that isn’t handled by the V8 engine is executed to not “block” the main execution thread. When the Call Stack encounters a web API function, the process is immediately handed over to the Web API, where it is being executed and freeing the Call Stack to perform other operations during its execution.

Let’s go back to our setTimeout() example above. When we run the code, the first console.log line gets pushed to the stack, and we get our output almost immediately. On getting to the timeout, timers are handled by the browser and aren’t a part of V8’s core implementation. It gets pushed to the Web API instead, freeing the stack so it could perform other operations.

While the timeout is still running, the stack goes ahead to the next line of action and runs the last console.log, which explains why we get that outputted before the timer output. Once the timer is completed, something happens. The console.log in the timer magically appears in the call stack again!

The Task/Callback Queue

Once the Web API finishes executing the task, it doesn’t just push it back to the Call Stack automatically. It goes to the Task Queue.

A queue is a data structure that works on the First-in-First-out principle, so as tasks get pushed into the queue, they get out in that exact order. Tasks executed by the Web APIs are pushed to the Task/Callback Queue which then goes back to the Call Stack to get their result printed out.

The event loop is a process that waits for the Call Stack to be clear before pushing callbacks from the Task Queue to the Call Stack. Once the Stack is clear, the event loop triggers and checks the Task Queue for available callbacks. If there are any, it pushes them to the Call Stack, waits for the Call Stack to be clear again, and repeats the same process.

Conclusion

While this is an introduction, the concept of asynchronous programming in JavaScript gives enough insight to clearly understand what is going on under the hood and how JavaScript can run concurrently and asynchronously with just a single thread.

To learn more about Event loop, you can check out: