Adam O'Grady

JavaScript Event Loop

A lot of people talk about JavaScript as an asynchronous, event-driven language but don’t dive further down on what that means or how best to take advantage of it. Even more important is to be wary of the pitfalls that this can involve which I’ll cover in later posts.

The most common asynchronous code you’ll find in JavaScript is usually Ajax (asynchronous JavaScript and XML) requests when dealing with client-side requests, or API calls/database access when dealing with server side systems like Node.js. However the simplest example that doesn’t involve network access or extra libraries is probably something like the following:

console.log("First");
setTimeout(function printThird() {
  console.log("Third");
}, 1000);
console.log("Second");

What happens here is:

  1. “First” is printed to the console
  2. The function printThird() is queued up to be executed after 1000ms using setTimeout()`
  3. “Second” is printed to the console
  4. After 1000ms has passed, printThird() is executed, printing “Third” to the console.

The act of queueing up code for later execution while still executing other code in the meantime is an example of JavaScript’s asynchronous nature. In more advanced examples you can send a request to an API and keep running other code until you get a response upon which you can trigger a set function.

An example of this using the XMLHttpRequest:

function reqListener () {
  console.log("Second");
}
var ajax_request = new XMLHttpRequest();
ajax_request.addEventListener("load", reqListener);
ajax_request.open("GET", "/");
ajax_request.send();
console.log("First");

In this case, the script does the following:

  1. Sends a HTTP GET request to the root directory of the current site
  2. Does not wait for a response
  3. Logs “First”
  4. When it receives a response it logs “Second”.

This breaks a common paradigm in many languages where code is executed from top-to-bottom of a block even if that means having to wait for a response before continuing execution. By queueing up “events”, JavaScript allows scripts to keep executing and deal with these events once they’re ready. While this does add cognitive load to the developer as they need to make certain what code will be executed at what time, it does allow obvious performance benefits.

Triggering asynchronous behaviour

The examples previously have discussed situations where an asynchronous response is required because otherwise execution would be delayed (timers, waiting for network responses). However what about the following code:

console.log("First");
setTimeout(function printThird() {
  console.log("Third");
}, 0);
console.log("Second");

This is the exact same as the first example and will print out the messages in the same order (“First”, “Second”, “Third”), even though we’ve specified 0 milliseconds for the timer. This is where we get into the tricky internals of the JavaScript event loop which I’ll discuss in another post. In short, what is happening is:

  1. “First” is printed
  2. printThird() is queued for execution after the current code block finishes
  3. “Second” is printed
  4. printThird() is executed
  5. “Third” is printed to the console

Why would we want to do this? It’s useful in a few circumstances:

  • It can break up complex calculations to prevent “script is busy” errors in the browser
  • It can yield execution time to allow for DOM refreshes in the browser
  • It can break up complex tasks to prevent one task from dominating execution (creating the illusion of multiple threads)