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
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.
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.
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!