Adventures in the JS Concurrency Model

Once in a while, you’ll hear a word or phrase that, if you look it up, reveals a whole new world to you. Examples include Bo-Taoshi, Flat Earth, and Battlebots. The most recent example for me was “Javascript Event Loop.” The event loop itself isn’t terribly complicated, but understanding it requires understanding basically the entire context within which Javascript gets run. So that’s what we’re learning about today! Let’s go!

Javascript differs from complied languages in that it needs a container within which to run. The container can be a browser like Chrome, or a server-side runtime like Node.js. This container splits into two parts – the engine and the runtime.


Image result for rusty engine

The job of a Javascript Engine is to compile our JS code down into a lower-level language, usually C or C++. Each of the most popular browsers in the world has its own engine which competes with the others. Chrome uses V8, Firefox uses Spidermonkey, IE uses Chakra and Safari uses JavaScriptCore. Incidentally, Spidermonkey was the first JS engine ever built, and I have to say, I think it’s pretty cool that 12 updates later it’s still in use. These engines all compile to C++, except for JavaScriptCore, which compiles to Objective-C.

Runtime Environments

Image result for web apis

The other part of the container for our JS is our runtime environment. Essentially, the job of the runtime is to provide us with JS objects and APIs that are useful and relevant to the work we might want to do in that particular context. People are often surprised by how much of the Javascript they write is not provided by the language itself, but by the runtime of the container they are using, be it a browser, Node or something else entirely. Here are some examples of functions that are provided by browsers and not the core JS language: document, XMLHttpRequest and setTimout. Again, these functions are not provided by the core Javascript language itself, but rather the runtime of whatever container wraps it.

Moving on…

As you probably know, JS is single-threaded, meaning it can only do one thing at a time. I think most of us have experienced the cognitive dissonance of holding that in mind while we somehow go ahead and do asynchronous programming anyway. The reason it’s possible is that our JS code doesn’t do all of that async stuff – our container does, which in most cases means our browser. Runtimes and the tools they provide are very context-dependent. For example, Chrome and Node.js both use the V8 engine, but while the Chrome runtime gives us access to the DOM and the window object, the Node runtime gives us require and Process. This is because Chrome, a browser, has different needs from Node, a server-side environment. So though the core language and engines are the same, the runtime environment is different, and provides different APIs.

The CallStack

You’ve probably seen the callstack in your browser’s debugger, but the details of how it works may yet elude you. In the broadest terms, the callstack shows us where we are in the execution of our program. It displays a list of callbacks, but when we look at them in the callstack, we refer to them as frames. The earlier a frame was called, the closer to the bottom of the stack it is. Every frame was  called by the one below it, except of course for the one at the very bottom. When a function on the top of the stack returns (i.e. completes), it pops off the top of the stack, leaving the one underneath is to either finish as and pop off as well or make another function call, adding a frame to the top of the stack. This goes on until the bottom frame completes and the stack clears.

Now. With async functions, it gets a little trickier. Let’s say we have an AJAX get request in our code. If it just ran in our normal code, it would block up our callstack while everything else waited for it to complete. Even mouse clicks and scroll actions would be blocked, added to a que of actions that would only affect the page once the AJAX request was complete. So these kinds of callbacks aren’t going to be part of the normal callstack. Instead, they get sent to the container’s runtime, and its web APIs assume responsibility for those async actions while the main JS code just continues on its merry way. Woohoo! Unblocked!

Okay, we sent our AJAX request through and now it’s finished! Huzzah! Okay so now what happens to the remainder of our function? Our call stack is busy with stuff, we can’t just cram it back in there all haphazard-like. No, the remainder of our async function goes to the message que.

The message que is like the waiting list to get into the callstack area. I’m calling it ‘the callstack area’ and not just ‘the callstack’ because it’s important to remember that a function can’t get into the middle or top of a callstack without being called by another function. Every frame in the callstack is being called by the frame below it, except of course the bottom frame. None of the functions in our message que will be added to an existing callstack – they’re going to start new ones. The message que is just a bunch of callbacks waiting to be the new bottom frame of the callstack.

The process that powers the movement of functions from the message cue into the callstack is called the Event Loop. The Event Loop is simply a while loop that says “While the callstack is empty, add the oldest function in the message que to the callstack as the bottom-most frame.” And thus async JS can work without blocking, and we come full circle.

Let’s take a look at a visual, courtesy of Assem Raj Baranwal to tie this all together. One part of this chart we did not discuss is called the ‘heap’ – this basically just refers to the place in our engine’s memory where our JS objects get stored. Don’t worry your little noggins too much about it this time around, we’re really just focusing on the Callstack/Message Que flow today.

Image result for js concurrency model

One thing that’s a little hard to see here is what happens when your program runs callbacks that are not Web APIs provided by the container. Do they go straight to the callstack? Nope, they just get in line at the Message Que. Any callback that fires from a click, scroll or mouse move event goes into the message que, right next to the async stuff.

So that’s the JS Concurrency Model! Fun, right? I highly suggest taking a look at Phillip Roberts’ talk about it as well. My favorite part was when we learned that XHR was just a Web API provided by browsers! This whole time! Let’s use this knowledge as a springboard to talk about more complex backend JS stuff in the future. See you next week!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s