If you've ever asked someday about JavaScript definition… you would get some answer like this:
JavaScript is a Single-threaded, non-Blocking, Asynchronous, Concurrent language!!
Really!,.. Nice!!
Concurrency, Asynchroniztion, Threading, Parallel Programming and a lot of these stuff here… it’s a mess of terms. Although you can easily use one of these terms to explain your idea, it’s also easy to get lost using the right term at the right place, Even experienced developers could miss accuracy choosing the correct word. And what i’m trying to do here is to go more deeply to get better understanding for the difference between these terms... focusing on JavaScript Asynchronous programming.
Before Asynchronization… What’s Synchronous code at first?!!
If you have ever written any (Hello World) program with any popular programming language like ( C, Java, C#, Python), This is a synchronous code, which in code is executed line by line in order they are wrote.
code:
Console.log('First Line');
Console.log('Second Line');
Console.log('Third Line');
Output:
First Line
Second Line
Third Line
Yes, you get this result as expected… this is synchronous code.
But before moving forward to asynchronous code, let’s go more in details about what happened really at JavaScript runtime with these lines.
JavaScript Runtime Engine:
From code execution view, We are focusing on one important component of any JavaScript engine (ex: V8, Google’s open source JavaScript engine that is used for Chrome and Chromium projects) which is Call Stack. in simple words, Call Stack is a Stack data structure where V8 stores information about functions that Are currently executed to keep track of the execution flow of the code.
Let’s analyze code like this:
function sayHello(name){
console.log('Hello, ' + name);
}
function greeting(name){
return sayHello(name);
}
greeting ('Adam');
- Initially, JavaScript runtime pushes a stack frame (let's say
main
) which represents the script itself or the current block of code. - Declaration of
sayHello
function, nothing really executed here. - Declaration of
greeting
function. - By running line 9, JavaScript runtime pushes
greeting
address at the top of the stack to be executed. - Inside
greeting
we are calling sayHello
function, that means JavaScript has to push sayHello
address also to the Call Stack. - Again, Inside
sayHello
function, JavaScript runtime found calling of console.log
function, so it pushes it also to the Call Stack to be run. - After
console.log
function return, JavaScript runtime pull the stack frame of console.log
and move back to the previous address at the Call Stack which is sayHello
. - JavaScript runtime finish executing
sayHello
function and pulls its stack frame from the Call Stack. - pulling the stack frame of
greeting
. - pulling the stack frame of main.
Now, What about Asynchronous code?!, And Why?!
With simple lines like the previous example we can’t find any problem executing this code, the program will run and finish its job smoothly, But what if we have some slow/blocking lines of code?!
Operations like accessing files or database or reading from network are considered very slow, and because of the nature of JavaScript running inside browsers, slow operation (ex: http request) could freeze your web page, so it’s considered a blocking code.
And here become the Asynchronous part. Because JavaScript runs inside Browsers, it must not executing any blocking code, it shouldn’t wait and freeze the web page otherwise users could get a terrible experience trying to browse even simple web pages. So, JavaScript is non-blocking by design and this means it wouldn’t freeze the execution and wait for blocking line of code, instead of this it uses Events and Asynchronous Callbacks techniques.
Events & Asynchronous Callbacks
At this approach, Asynchronous Callbacks provide a solution to avoid blocking code. And the Callback is a function which is registered to be executed when an event occurred, JavaScript will not wait for the blocking code to be finished, rather than this, it will continue executing the next lines and get back again to the callback when the event is fired.
A fake callback would look like:
httpRequest('http://www.google.com/',function myCallBack(){
console.log('Hey!!');
});
And this means that after finishing the httpRequest
(blocking code), myCallBack
function would be executed (callback).
Simple?… OK!?
OK, but now I can see JavaScript can do more than one thing at the same time, it can execute the main thread code and also handle events as well at the same time. Isn’t this kind of Concurrency or something?! Aren’t you said JavaScript is single-threaded and it can do just one thing at a time?!!
One step deeper...
Well, before answering this, it’s time again to get another step deeper into JavaScript environment. And let me introduce new friends to you… please welcome Event-Loop, Message Queue and WebAPI.
If we imagine JavaScript runtime environment, it would be something like this:
-WebAPI: it’s the part of the browser that is responsible on the external APIs, DOM APIs and most of blocking IO code like accessing files, network requests or even Timers (setTimeout
). It’s job is to fetch back the registered event callback when the browser fire a specific event.
-Message Queue: it’s a simple queue (FIFO) where callbacks from WebAPI come and are stored in order.
-Event Loop: its job is watching the Call Stack, if empty, it pushes the next task at Message Queue into the Call Stack to be executed.
May be this is new for you, Blocking actions like accessing files or network is not a part of JavaScript runtime (V8) itself, even setTimeout
, it comes from the hosting environment which is the web browser (or NodeJS), Browser is much more than JavaScript runtime engine.
For code like this :
console.log('first');
setTimeout(function foo(){
console.log('second');
}, 1000);
console.log('third');
Output:
first
third
second
This happens as we registered function foo
to be run after 1000ms. so the runtime runs the first line , then the third line and after 1000ms it back again to execute setTimeout
callback and here is what is going at Call Stack and Message Queue.
In setTimeout case, WebAPI setups a timer with interval 1000ms, and waiting for this period to be triggered. Once it’s triggered, WebAPI enqueue the timer Callback function foo
into the Message Queue.
Message Queue is a place where Callbacks from events are stored in order and get ready to be executed. And here comes the part of Event loop.
Event loop keeps watching the Call Stack and when Call Stack is empty Event loop pushes the top task at Message Queue into the Call Stack to be executed.
We can simulate this process by this:
- the initial stack frame to start executing the code.
- JavaScript runtime pushes
console.log('first')
to the Call Stack to be executed, and we got "first" at the output. - Calling
setTimeout
to setup a timer with callback function foo and interval 3000ms - WebAPI creates a this timer and waiting for 3000ms.
- JavaScript continue running the code and pull the stack frame of
setTimeout
- JavaScript moves to the next line and pushes
console.log("third")
to the Call Stack and we got "third" as output. - JavaScript pull the top stack frame.
- JavaScript pull the main stack frame.
- After 3000ms, and Timer event is fired.
- WebAPI enqueue the timer callback
foo
to the Message queue to get ready for execution. - Event loop checks the Call Stack, and if empty, it pushes
foo
function into the Call Stack. - executing the body of foo function,
console.log('second').
- pulling off
console.log('second')
from the Call Stack - pulling off foo stack frame.
And this is the way how JavaScript can handle Concurrency with Single-Threaded non-Blocking design...
Please note:
Event loop pushes a new task into Call Stack only when the Call Stack is empty, setting setTimeout(function(){}, 0)
-(aka Zero-delay case)- doesn’t mean it will fire immediately, it would be executed when it’s the next at Message Queue AND the Call Stack is empty, also setTimeout(function(){}, 1000)
does not guarantee to be executed exactly after 1000ms , but for sure it guarantees not to be executed before this.
Conclusion:
Yes, JavaScript is single-threaded by itself, it just can do one thing at a time, and also it’s a non-blocking by design, and it handles Concurrency via Event loop & Asynchronous Callbacks by the help of the browser or the hosting environment.
References :