Currently I am trying my hand at NodeJs. Coming from a Python background the asynchronous execution of code was a mystery to me. So, I had to really understand what’s going on behind the scenes and why things happen in a surprising order in this part of the world. Last few days I spent some time reading blogs and talks on this subject. I hope I can share with you what I have learned about the mystery of asynchronicity of Javascript.
Helloworld of Asynchronous Javascript
console.log('Start...');
function callback(){
console.log('Middle part of the code...!');
}
const executeAfter = 0;
// Execute `callback` after `executeAfter` milliseconds
setTimeout(callback, executeAfter);
console.log('Finished!');
----------------OUTPUT------------------------
Start...
Finished!
Middle part of the code...!
So, what’s happening here? One would expect the output to flow in normal/synchronous order, start, middle and finish. But that’s not the case. Why?
Before talking about the why, let us talk a little bit about Javascript. Specifically, about what runs Javascript code.
Be it a browser or Nodejs the common factor is a Javascript engine which actually runs Javascript code. In the case of Chrome and Nodejs, that component is V8. The V8 engine consists of three main parts, namely the parser, Ignition (the interpreter) and Turbofan (the optimizing compiler).
code ┌────────┐ AST ┌───────────┐ Bytecode ┌──────────┐
-------> │ Parser │-------->│ Ignition │--------->│ Turbofan │---->Machinecode
└────────┘ └───────────┘ └──────────┘
The V8 parser generates an abstract syntax tree after parsing the Javascript code. The AST is interpreted by Ignition to generate the corresponding Bytecode which in turn is used by Turbofan to generate native machinecode to execute.
Now, let’s talk about why the above code runs in that order. The culprit here is setTimeout
, it’s a special function.
The setTimeout
is an asynchronous function call which will execute the callback function after the given
time period of executeAfter
. But the execution doesn’t wait till this is done, it moves onto the next step.
Hence the weird order, or the asynchronous order.
If it were just V8, the above code will not even work, because setTimeout
is not part of the Javascript specification
and hence V8 doesn’t implement it. Also, V8 doesn’t provide any of the other asynchronous features that we normally use
in our apps and sites. In other words Javascript as implemented by V8 and other Javascript engines are meant for synchronous
execution and is similar to other languages like Python and Ruby in that regard.
So who provides the asynchronous features to Nodejs?
The notorious Event Loop
!
libuv and The Event Loop
Meet libuv
, the other major component of Nodejs. It is the asynchronous I/O engine of the Nodejs runtime
environment that provides Nodejs with necessary features to take care of I/O tasks like reading a file, network calls
and other asynchronous apis like setTimeout
, setInterval
, setImmediate
, etc.
If you refer Event loop and nexttick you will see a diagram depicting the eventloop. I am adding below a simplified loop that I feel relevant to this discussion.
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
Interrupts │ ┌─────────────┴─────────────┐ ┌───────────────┐
┌──────────────────<──────────────────┤ │ poll │<─────┤ connections, │
│ │ └─────────────┬─────────────┘ │ data, etc. │
│ │ ┌─────────────┴─────────────┐ └───────────────┘
│ └──│ check │
│ └───────────────────────────┘
│ The EventLoop
│ ┌───────────────────────────┐
│ │ process.nextTick │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ Microtasks │
└───────────────────────────┘
Not Event Loop
And the corresponding async actions that happen in each of those phases of the eventloop.
┌───────────────────────────┐
┌─>│ setTimout,setInterval │
│ └─────────────┬─────────────┘ ┌───────────────┐
Interrupts │ ┌─────────────┴─────────────┐ │ incoming: │
┌─────────<───────────│ │ poll and I/O callbacks │<─────┤ connections, │
│ │ └─────────────┬─────────────┘ │ data, etc. │
│ │ ┌─────────────┴─────────────┐ └───────────────┘
│ └──┤ setImmediate │
│ └───────────────────────────┘
│ The EventLoop
│ ┌───────────────────────────┐
└──┤ process.nextTick │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ Promise │
└───────────────────────────┘
Not Event Loop
Each phase of the event loop has its own queue. At each phase the event loop processes the queue
until it’s exhausted and moves onto the next. At each transition to the next phase it peeks outside the loop
to process.nextTick
queue and the micro-tasks queue. If there are callbacks in the process.nextTick
queue,
they are run until the queue is exhausted. Next it checks the microtasks queue, which contains the callbacks
associated with Promises
. They are run next. Only then the event loop continues with the next phase.
Please note that these two queues are not part of the eventloop.
The phases of the event loop are roughly as explained below:
1. Timers
The callbacks from `setTimeout` and `setInterval` calls are executed
if the respective timers are done. Note that the callbacks associated with the timers are
enqueued only if the respective timer crosses the given threshold.
2. Poll
The event loop polls for I/O events until its time to execute the timer callbacks.
That is if any timer crosses its threshold while the loop polls for I/O, the eventloop
loop backs to timer phase. If not the available I/O callbacks are executed.
3. Check
Here any `setImmediate` callbacks are executed.
One more important detail to remember; the callbacks from eventloop gets executed only if the normal callstack maintained by the V8 engine for the execution of synchronous calls, is empty. This is the reason why all the synchronous calls gets executed prior to the handling of asynchronous callbacks.
If I have done a decent job of explaining the loop you should be able to predict the order of execution of the below code or at least explain it’s output.
setTimeout(() => console.log('set timeout'), 0);
setImmediate(() => console.log('set immediate1'));
process.nextTick(() => console.log('next tick1'));
console.log('Console log after next tick1');
Promise.resolve().then(() => console.log('promise1 resolved'));
Promise.resolve().then(() => console.log('promise2 resolved'));
Promise.resolve().then(() => {
console.log('promise3 resolved');
process.nextTick(() => console.log('next tick2 in Promise3'));
setImmediate(() => console.log('set immediate2 in Promise3'));
});
Promise.resolve().then(() => console.log('promise4 resolved'));
setImmediate(() => console.log('set immediate3'));
setImmediate(() => console.log('set immediate4'));
process.nextTick(() => console.log('next tick3'));
console.log('Console log at the end!');
----------------OUTPUT------------------------
Console log after next tick1
Console log at the end!
next tick1
next tick3
promise1 resolved
promise2 resolved
promise3 resolved
promise4 resolved
next tick2 in Promise3
set timeout
set immediate1
set immediate3
set immediate4
set immediate2 in Promise3
Summary
Javascipt is synchronous. Nodejs is asynchronous because Nodejs is V8 + libuv
. V8 runs Javascript, libuv provides asynchronous I/O. And
all the asynchronous magic happens in The Event Loop
provided by libuv
.
Well, that’s it for now! Thanks for reading my blog and I hope you got something out of it, Cheers!