JavaScript Asynchronous Life Cycle Simply Explained

JavaScript Asynchronous Life Cycle Simply Explained

Reading Time: 10 minutes

JavaScript has a lot of power today. It drives all the front end applications on browsers, delivers back end applications (NodeJS) and more. JavaScript is not exceptional from other programming domains, means that as a JavaScript professional developer you have to know a few things about the internals of the language in order to create great applications. There are few misconceptions and lack of knowledge that some JS developers face along their career, and gathering this knowledge and breaking those misconceptions can of course help them to create even greater products. In this post we will bust the myth of “JavaScript is Asynchronous”.

JavaScript Is Not Asynchronous

The most common misconception among JS developers is that JS is asynchronous language. Actually I cannot think of a programming language that indeed is asynchronous, and what the hell does that mean “Asynchronous Language” anyways?

Asynchronous term in JavaScript is related to the way of JS deferring tasks to execute in a later stage of code run time. The way JavaScript runs it asynchronous calls are handled by the context in which JavaScript is running (browser, NodeJS, etc). For example the most simple asynchronous function setTimeout is not native JavaScript function, it is implemented differently in the browser and in nodeJS. Actually JavaScript is processing and executing it’s commands one line at a time, line by line, or in other words it is a “synchronous language” or in the real word terms: blocking single thread programming language. To examine this write a small program with a big while loop followed by a console.log, you can see that the console.log isn’t executed until the loop finished.

There are a few ways to deal with asynchronous tasks (such as reading and writing to database, dealing with OS I/O, etc). Callbacks, promises and async/await functions and more, are the way JavaScript enables you to execute asynchronous tasks and get back the responses. Those all are a syntactic way to deal with those problems, but what happens behind the scenes?

Behind The Scenes

The part that manages The execution logic called the V8 engine, you might have heard about it here and there in the context of chrome browser and NodeJS. V8 is the JavaScript engine that drives the chromium project (chrome browser is based on chromium). NodeJS is implemented on top of V8 also.

Stack

When we execute synchronously our code, line by line, yes, even your asynchronous functions, they are pushed into something called the stack. I won’t get too much into details here. In short, each function call creates a space in memory called frame that contains all information for it to be executed, for example the arguments, and this fame is pushed to the top of the stack. When the function returns, its frame is popped out of the stack. Using a stack is a very familiar way to deal with synchronous operations, because you can tell exactly when a function is running and when it is returned. Actually you might have used this stack when debugging your application. When you get an execution error on the browser for example, you can see the stack trace is printed out, this is easily done because of the stack data structure. And of course the mighty stack overflow error is thrown when the stack gets bloated with too many frames, in chrome browser the limit is 16000 frames.

Heap

The heap is a region in the memory where arbitrary data can be stored in 
unordered way. (The ECMAScript language specification doesn’t specify definition on memory layout, so what does it in real life is the interpreter). In other words, V8 (written in c++) handles memory allocation in a way that it allocates the memory in the heap and uses pointers to those variables in the stack which is much more ordered and fast. V8 engineers of course made a lot of optimizations to this abstract review I wrote, so for example atomic variables such as booleans are actually stored in the stack, and much more optimizations also done in this engine. The heap has also to do with the Garbage Collector but we will leave this to another post.

Queue

The queue is the core of this post. The queue is a place in memory which queues messages associated with the asynchronous tasks (callbacks) created by your program. If a callback is not created, no message is pushed into the queue, for example mouse move events without callbacks. The messages in the queue are processed in order (FIFO) as the stack gets empty (or other logic implemented by the interpreter), and what manages this execution is something called the Event Loop.

The event loop is (as the name suggests) an infinite while loop that queries the stack and the queue for tasks to do. When the stack gets empty it takes the next task from the queue and pushes it to the stack (as a frame) there it would be executed like a regular function. This is the reason asynchronous code is hard to debug, because those functions pushed to the stack are out of context when the time of execution comes, so for example the stack trace won’t help us too much (this is of course gets optimized with each version of JS released).

Resources