(Pt1): Internals Disclosed!
Last updated
Last updated
In this article, we’ll be answering the question of how can we achieve asynchronous programming in JavaScript. However, since this topic could get quite lengthy, I decided to divide it into several stories.
In this first story, we’re going to have a brief overview to introduce you to the behind scenes of asynchronous JavaScript. Indeed, I think that this could help you build a solid foundation to write and understand asynchronous code more efficiently in JavaScript.
So to begin, I’d like to start with the following statement: JavaScript is single-threaded, synchronous, and blocking by nature.
What do mean by that? Let’s elaborate…
The JavaScript engine is the one responsible to execute your JavaScript code. In fact, JavaScript is said to be single-threaded, synchronous, and blocking because the engine can execute only one instruction at a time and the order it appears in your code.
The JavaScript engine is composed of two main elements: the Heap Memory, and the Call-stack.
So as illustrated in the image above, the JavaScript engine has got only one single call-stack which means that it is able to execute only one task (one execution context) at a time.
It is known that the stack structure follows the LIFO (Last In, First Out) principle. This is why the instructions are executed sequentially in the order they appear.
Now, what about the fact of JavaScript is blocking in nature?
To explain this, I’d want you to copy/paste the following example in your browser’s JavaScript console (which can be found in the developer tools) and hit enter to run the code.
The thing is after invoking the functions A, B, and C respectively in this order, we never see C being executed and more likely your browser got stopped working.
Why is this happening?
This is due to the fact that the JavaScript engine blocks at the B function which never ends (contains an infinite loop)…This explains what we’re calling JavaScript blocking by nature. It doesn’t move into the next instruction until it finishes the current one.
Now, assuming in some real-life example, that function B is going to fetch billions of data from an external source. So, would it be better if the main thread of execution — the JavaScript engine — waits for it (doing nothing) to finish fetching those data, or move on to execute some other synchronous code (getting busy and productive) before coming back to it again when the data is ready?
The second option would be wiser, isn’t it? Especially if the execution time is an important factor for you…Actually, this is what we mean by asynchronous programming. In other words…
Asynchronous programming refers to the programming paradigm where the occurrence of events is independent of the main program flow and ways to deal with such events.
However, how’s this could be possible knowing that the JavaScript engine can only execute synchronous code?
When the JavaScript engine meets an asynchronous event in your code, the only thing it will do for you is to bind the work to someone else while it keeps on executing the rest of the main program. Moreover, the JavaScript engine will not care about how someone else does his work, the only thing it cares about is the final result of the asynchronous operation.
Now, you might be asking yourself a question like: who’s this mysterious “someone else”?
In fact, the “someone else” here is a set of mechanisms that without them asynchronous programming wouldn’t be possible in JavaScript. Let me introduce you to our new friends: the event loop, the callback queue, and a handful set of APIs.
So, the event loop is the one responsible for monitoring the callback queue and the call-stack. If the call-stack is empty, it will take a callback function from the head of the queue and push it to the call-stack — this explains why, in JavaScript, asynchronous code is processed always after completely dealing with the synchronous code first.
The APIs on the other hand is a set of tools that help JavaScript to do the stuff it cannot do by itself (I/O operations mainly). For example, the famous setTimeout() function is asynchronous and belongs to the Web APIs in the browser.
By the way, the JavaScript and all our above-mentioned new friends represent what we call the JavaScript Runtime. One example of this would be the browser you are working on. It contains a JavaScript runtime that is responsible to run your JavaScript files. Another well-known example is the Node.js runtime.
Now, back to our previous code sample. Let’s tweak it a bit by incorporating some asynchronous code,
If you try this piece of code on your end, you would get something like the following printed to the console,
Where ‘2’ is printed after 2 seconds as the last thing even though function B was invoked before function C. Let’s explain what’s going on here…
At line 11, A gets pushed to the call-stack for execution, and ‘1’ is printed to the console, then gets popped off from the stack.
After, when the JavaScript is at line 12, it pushes function B to the call-stack and starts executing it. The function B contains the asynchronous setTimeout() function (line 5). Therefore, the JS engine is going to bind the work to the appropriate API (Web APIs in our case) to deal with it.
Meanwhile, the JS engine has already jumped to the execution of line 13 which is invoking the function C. It pushes it to the stack for execution. As a result, It prints ‘3’ to the console and popped it off from the stack.
When the two seconds are over(delaying by two seconds is actually the work we wanted from the API), the callback ( ()=>console.log(‘2’) ) is pushed to the callback queue. At that time the event loop is always monitoring the callback queue and the call-stack. Therefore and because our call-stack is empty, the callback function ( ()=>console.log(‘2’) ) is moved to the call-stack for execution. Finally, ‘3’ gets printed to the console.
The bottom line from the above explanation is that our code is no longer blocking. Thanks to the ability we have to run asynchronous code in the JavaScript runtime…
The takeaway from this first part of the article is,
JavaScript is always synchronous. Despite the fact of using some asynchronous calls in your code, these are always fired synchronously.
One last thing to add is that there are two types of asynchronous coding styles in JavaScript: the old-school callback style, and the promise style (async/await included).
In the following stories, we’ll see each one of these styles in much more detail.
That’s it! Hope you’ve learned something new today.