The JavaScript Event Loop is a crucial concept to understand when working with JavaScript. In this blog post, we will delve into the details of how the event loop works and how JavaScript handles asynchronous functions.

Introduction

The Event Loop is an integral aspect of JavaScript. If you have been programming with JavaScript for a while, you might not have a deep understanding of how things work under the hood. However, it is always beneficial to have a basic understanding of how the Event Loop operates.

JavaScript operates on a single thread, meaning that only one thing can happen at a time. While this may seem limiting, it simplifies programming by removing the need to worry about concurrency issues. However, it is crucial to write code that does not block the thread, such as synchronous network calls or infinite loops, to ensure smooth execution.

Blocking the event loop

Any JavaScript code that takes too long to return control to the event loop can block the entire page, including the UI thread. This means that users cannot interact with the webpage until the code finishes executing. JavaScript is designed to be non-blocking, with most I/O operations, such as network requests and file system operations, being asynchronous. Callbacks, promises, and async/await are commonly used to handle asynchrony in JavaScript.

The call stack

JavaScript uses a call stack, which is a Last In, First Out (LIFO) structure. The event loop continuously checks the call stack for functions that need to run and executes them in order. The call stack is also responsible for generating error stack traces to help in debugging.

A simple event loop explanation

To illustrate how the event loop works, let’s look at a simple example:

const bar = () => console.log('bar');

const baz = () => console.log('baz');

const foo = () => {
  console.log('foo');
  bar();
  baz();
};

foo();

In this code, foo() is called first, followed by bar() and baz(). The call stack looks like this during execution:

foo
bar
baz

Queuing function execution

In some cases, you might want to defer a function until the call stack is empty. The setTimeout(() => {}, 0) function can be used to achieve this. Consider the following example:

const bar = () => console.log('bar');

const baz = () => console.log('baz');

const foo = () => {
  console.log('foo');
  setTimeout(bar, 0);
  baz();
};

foo();

Surprisingly, the output of this code is:

foo
baz
bar

In this case, bar() is deferred until the call stack is clear, resulting in the changed order of execution. This behavior is possible because of the Message Queue.

The Message Queue

When setTimeout() is called, the browser or Node.js starts a timer. Once the timer expires, the callback function (in this case, bar) is placed into the Message Queue. The event loop gives priority to the call stack, executing everything it finds in the call stack before picking up tasks from the Message Queue.

ES6 Job Queue

The ES6 Job Queue was introduced in ECMAScript 2015 and is used by Promises and async/await functions. It allows the execution of the result of an async function as soon as possible, instead of waiting for the call stack to clear. Just like a fastpass ticket at an amusement park, the Job Queue allows the async function to be executed immediately after the current function completes.

Example:

const bar = () => console.log('bar');

const baz = () => console.log('baz');

const foo = () => {
  console.log('foo');
  setTimeout(bar, 0);
  new Promise((resolve, reject) => resolve('should be right after baz, before bar'))
    .then((resolve) => console.log(resolve));
  baz();
};

foo();

In this example, the output is:

foo
baz
should be right after baz, before bar
bar

This illustrates that Promises and async/await functions are placed in the Job Queue and executed promptly, making them more efficient compared to traditional asynchronous functions.

In conclusion, understanding the JavaScript Event Loop is essential for writing efficient and non-blocking JavaScript code. By grasping how the event loop handles asynchronous operations, you can write code that executes smoothly and optimally.