The Main Event… Loop

A quick look at the JavaScript concurrency model

JavaScript is a single-threaded language, which means it can only process one command at a time, but it is also asynchronous which means that events can happen outside of the main flow of your program. At first glance, those two ideas seem like they might be contradictory, so let’s take a deeper dive into what’s going on under the hood.

First, let’s take a look at what the runtime environment actually looks like.

As you can see the runtime consists of a stack and a heap. We don’t need to worry about the heap to talk about the event loop. Basically, it’s a bunch of memory that can be allocated as needed and referred to by the stack. What we are interested in is the stack itself.

Remember how I said JavaScript was single-thread? Well, that single-thread is the call stack. As your program runs, each function call is pushed onto the stack to be executed. Then as it returns, it is popped off the top.

Since the call stack is, well — a stack, we can only execute the last item pushed onto it.

So, what happens if that function takes a long time to execute? That’s called blocking. Blocking is when the execution of your code is held up or blocked by some process, and nothing else can happen until it completes.

If that’s the case, why doesn’t our entire program freeze every time we make an HTTP request? To understand this, we need to take one giant step back and look at the bigger picture.

Web APIs

While it’s true that the JavaScript runtime can only do one thing at a time, it exists within our browser, and our browser has some extra stuff to help make things run smoothly.

That ‘stuff’ is Web APIs. Client-side JavaScript has many APIs available to it. They aren’t part of the core JavaScript language, but they are built on top of it and provide extra functionality. Web APIs come in two categories:

  1. Third-party APIs (which are not built into the browser) include things like the Spotify API or the Watson APIs, where you make some sort of request through a server and receive a response.
  2. Browser APIs function similarly to third-party APIs but are built directly into the browser. Browser APIs expose data from the browser and surrounding computer environment. A lot of the things that you constantly use and probably think of as defining features of JavaScript are actually browser APIs. Some examples include the DOM API which allows us to manipulate HTML and CSS, the Fetch API for making HTTP requests, and many more including Canvas, WebGL, and the Web Audio API.

The browser APIs are capable of executing their own code outside of the call stack. You can think of them as separate threads that your code can make calls to but not access directly.

What happens when something you started in a browser API completes? Say you executed a fetch and got a response - we don’t want the Fetch API pushing that response to the call stack whenever it randomly completes, or we could screw up the code that is currently running. We need something to hold onto all of those responses until the call stack is ready for them. That’s where the message queue comes in.

The Message Queue

The message queue is responsible for holding all of the messages that are generated from processes completed by browser APIs. As the call stack empties, the messages are executed (oldest first because it’s a queue) and their callback functions are added to the call stack. We’re still missing one vital piece of the puzzle. We need something to regulate the flow between the call stack and the message queue. That something is the Event Loop.

The Event Loop

The Event Loop is responsible for determining if the call stack is ready, and if it is, then executing the next message in the queue.

This setup, based on the event loop, is called a concurrency model, and it’s very different from a lot of other languages. One of the interesting things about it is that because of it JavaScript is non-blocking.

Cool, but what does that mean?

To be non-blocking means that instead of leaving slow tasks sitting on the call stack holding up the execution of the rest of your code, JavaScript executes them asynchronously by delegating them to the browser APIs. Once the asynchronous process is completed, the JavaScript runtime relies on the event loop to process the message as soon as the call stack becomes available.

So that’s how JavaScript is able to be both single-threaded and asynchronous. I hope this post was helpful in understanding more about how JavaScript uses the event loop to work it’s magic.

  • If you want to learn more, this talk by Philip Roberts is a really great resource.