DEV Community

Cover image for JavaScript: The Event Loop
Sean Tansey
Sean Tansey

Posted on • Edited on

JavaScript: The Event Loop

Many developers are aware that JavaScript is a single-threaded language. When executed in a runtime such as your web browser, JavaScript code runs synchronously, only ever able to do one thing at a time. But if thats the case then how are we writing all this fancy asynchronous code with callbacks, promises and async/await? The secret lies in the event loop.

The Basics

There are a few different components to understand that make up the event loop:

Stack

A stack is a data structure that operates in a LIFO (Last In, First Out) order. When we execute JavaScript functions they are added to the call stack and referred to as a frame.

For example lets say we had the following code:

const a = (num) => {
    return num + 5
}

const b = (num) => {
    return a(num)
}

b(15)
Enter fullscreen mode Exit fullscreen mode
  • function a(): takes in a number as a parameter and returns that number plus 5

  • function b(): takes a number as a parameter and then calls function a() passing in that number as a parameter

  • we call function b() passing in 15 as the parameter

The order of operations of this code is as follows:

  1. When calling b(), our first frame is created and added to the stack

  2. When b() calls a(), a second fame is created and added on top of the stack

  3. a() executes returning the value of 15 and is removed from the stack

  4. b() returns the value it received from a() and is removed from the stack, leaving the stack empty

While LIFO is great and all, it does have some limitations, what happens if we have to make some data calls that take a considerable amount of time?

// takes 1 second to execute
const a = () => {
    return 'a'
}

// takes 60 seconds to execute
const b = () => {
    return 'b'
}

// takes 1 second execute
const c = () => {
    return 'c'
}

a()
b()
c()
Enter fullscreen mode Exit fullscreen mode
  1. a() is added to the stack as a new frame, executes in 1 second and is removed from the stack
  2. b() is added to the stack as a new frame, executes in 60 second and is removed from the stack
  3. c() is added to the stack as a new frame, executes in 1 second and is removed from the stack

We can see here some of the limitations of the call stack, while function b() is executing for a full minute, nothing else can be done. This is referred to as blocking, and to create smooth experiences on the web we try to minimize this as much as possible.

Heap

The heap is where memory allocation occurs, storing all of your variables in your program. The stack and heap combine to form your JavaScript runtime. For the purposes of this article we can mostly ignore the heap.

Web API's

There are a number of API's provided by our browsers, such as the HTML DOM (Document Object Model), setTimout(), or the fetch API. These exist outside of the JavaScript runtime and combined with the queue below is what allows us to execute functions asynchronously, without blocking the call stack.

Queue

A queue is a data structure like the stack, but unlike the stack it operates in a FIFO (First In, First Out) manner. When we perform asynchronous operations through web API's like fetching data, upon completion they are added to the queue and are referred to as a message.

Event Loop

Ahh.. now we're finally getting somewhere! The event loop is where all of this asynchronous magic comes together. The event loop is the process by which messages are removed from the queue, and their callback functions are added to the stack as a new frame.

Putting it all together

// takes 1 second to execute
const a = () => {
    return 'a'
}

// takes 60 seconds to execute
const b = () => {
    return fetch('http://mockURL.com/example')
        .then((response) => {
            return response.json()
        })
}

// takes 1 second execute
const c = () => {
    return 'c'
}

a()
b()
c()
Enter fullscreen mode Exit fullscreen mode

Steps:

  1. Function a() is added to the call stack as a new frame, the code executes and is removed from the stack
  2. Function b() is added to the stack and the fetch api is called
  3. Handling the call is done within the fetch api itself, so function b() can now be removed from the stack. The fetch api continues to handle the request behind the scenes
  4. Function c() is added to the stack, executes and is removed
  5. Upon completion of the fetch request the data returned from the api, as well as the callback function (in our case return response.json()) is added to the queue as a message
  6. Since the stack is now empty once again the event loop runs, moving function b()'s callback with the api response data into the call stack
  7. The call stack executes function b()'s callback and removes it from the stack

Image description

Top comments (0)