Asynchronous JavaScript demystified

We are able to write asynchronous JS code, but JS itself doesn't have asynchronous behavior built into it. Perhaps surprising, but JS being a single-threaded language, at a time only a single chunk of code can get executed. That means if there is a task which take several seconds to get completed there is no way to preempt that and we are stuck waiting. But thankfully, JS engine doesn't run in isolation. It runs in a hosting environment and this hosting environment has mechanism to handle multiple chunks of code.

So how are we able to run the asynchronous code?

Before answering this, we need to understand few more concepts.

Call Stack : It is responsible for keeping track of all the operations in line to be executed. Whenever a function is finished, it is popped from the stack.

Web APIs : This is the place where asynchronous operations are waiting to be completed. Based on the command received from the call stack, the API starts its own single-threaded operation.

Event queue : This is a FIFO structure that receives callback of asynchronous operations/promises which are ready to get executed.

Event Loop : It continuously monitors whether the call stack is empty. If found empty it looks into the event queue, and dequeues any callback ready to be executed into the call stack.

image.png

We have the building blocks ready, now let's have a look at an example to understand what happens during the execution of code.

console.log('1');
setTimeout(()=>{
  console.log('2')
},1000);
console.log('3');

Output of the above snippet will be :

1
3
2

First the console.log('1'); is put onto call stack and it gets executed, and popped off the stack. 1 is printed on the console.

Then setTimeout(()=>{console.log('2')},1000); is pushed on the stack. Now setTimeout is an API provided to us by hosting environment, browser in this case. So it temporarily disappears(pop) from the call stack and passed to browser web api and a timer is set there for 1000 milliseconds.

Next, console.log('3'); is pushed on the stack, it gets executed and popped off. 3 is printed on the console.

After the completion of 1000 milliseconds, web api cannot directly give the callback to the call stack for execution as there can be some other code being executed at that moment. So it gives the callback to the event queue.

Now for all this time it was the event loop which was making sure that the call stack is never empty. At this point the call stack is empty and there is a callback ready to be executed waiting in the event queue. So event loop sends this callback to the call stack. It gets executed and this time 2 is printed to the console.