JavaScript Event Loop: The Concept

In order to work with JavaScript (in other words, designing any website; and more recently, the server-side node.js platform) it is necessary to understand how the Event loop works.

image

Event Loop

The Event Loop, as its name suggests, is a loop which processes events one after the other. It usually takes on the form:

while(waitingForNewEvent())
{
  processNextEvent(newEvent);
}

The loop will handle every event as it comes in, in a FIFO (First In First Out) approach. When an event is triggered, it will be added to the Event loop queue as a message to be processed and has to wait its turn. Once the event moves to the front of the queue, the JavaScript engine will execute the event. Once an event starts getting processed, it cannot be preempted by another event before it completes. This feature is called Run-To-Completion and is quite useful for programmers to understand the nature of JavaScript processing. No new events can kick-off during this period, i.e., Javascript is deterministic.

When I say kick-off, I mean a new event can’t start processing. But nothing stops a new event from getting on to the queue. After all, a queue is meant for one thing: waiting!

The queue mentioned above is called the Message queue, and each event triggered will create a new message and add it to the queue for processing. The main function of the Event loop is to process the messages (and the corresponding event) in the order they were added to the queue.

Say, you are calling 2 events one after the other, and the 2nd event needs data that needs to be modified/generated by the first event. As a Web Developer, you expect the 2 events to be processed one after the other. Well then, you’re safe working with JavaScript! Because JavaScript has single-threaded processing built into it. This is referred to as the “Concurrency Model” of the JavaScript engine.

Example

setTimeout(function() {
  console.log("2");
}, 0);
console.log("1");

The above code will print [1, 2]. Because the function inside setTimeout() is only executed once the current event queue is empty i.e., after the last console.log() line is executed.

Note: Check this nifty tool created by Philip Roberts to better visualise how the Event Loop works! Rerun the sample code and just look at what happens. It makes it so easy to understand how exactly JavaScript handles events.


Concurrency

The Concurrency Model has an interesting side-effect: Since the events are processed in order, the callback functions will never be called before the calling function finishes executing!

Promises are made possible due to this aspect of the JavaScript language. And they’ve made our life simpler, wouldn’t you say?!

Javascript is single-threaded and non-blocking! Feels like a contradiction, but it isn’t. Let me explain.

Since events are processed in the same order they’re added to the Message queue, you would expect IO operations to block further processing till they finish. But it ain’t so. :) Any IO operation is performed by firing events to relevant Web APIs, with callback functions attached to these events. The callback functions enable Javascript to perform IO operations in a non-blocking manner.

Even though JavaScript is a single-threaded, blocking, synchronous language, the environment in which JavaScript is executed allows asynchronous function calls to be performed. By running IO operations through such APIs, JavaScript can handle multiple things without blocking the main thread.

Example

Here is a sample code for a Submit button used for comment submission. The submit function uses an AJAX call to send the comment to the server, with callback functions for success/error (done/fail) handling.

$("#comment-form").submit(function() {
      event.preventDefault();
      var form = this;

      $.ajax({
        type: $(this).attr("method"),
        url: $(this).attr("action"),
        data: $(this).serialize(),
        contentType: "application/x-www-form-urlencoded"
      })
        .done(function(data) {
          console.log(
            "Your comment has been submitted."
          );
        })
        .fail(function(err) {
          console.log(
            "Sorry, there was an error with your submission."
          );
          $(form).removeClass("disabled");
        });

      console.log("Submit function is done!");
      return false;
    });

The comment submission is sent, and the last console.log() is executed without any delay. Whenever the request call returns, it will execute the console.log within!

This illustrates the non-blocking nature of JavaScript. If the user interacts with the website during the submit call, the website is able to react to the user. The Mozilla docs even go on to say that JavaScript is “Never blocking”.


ES6 Promises

The only exception to the Message queue concept, is how (or rather when) the Promises are resolved. You would expect a Promise which resolves to be added to the end of the queue for processing. But a Promise is designed to wait the least time possible before execution!

How would you ensure that a Promise will be executed soon without compromising the Run-To-Completion feature? By adding it to the beginning of the queue! So that it gets executed as soon as the current function completes. This will guarantee that an asynchronous call will not have to wait too long after it returns to begin execution.

I hope this article has shed some light on the Event Loop of the JavaScript language and given you better understanding of the Concurrency Model.

If you want to understand the Event Loop in depth, there are several resources already available. The ones I found useful are given below:

Note: The cover photo is by Pankaj Patel on Unsplash!