This article was originally posted on Medium as an extension to my article series on NodeJS Event Loop on Medium:
In NodeJS Event Loop article series, I explained,
- Event Loop and the Big Picture
- Timers, Immediates and Next Ticks
- Promises, Next-Ticks, and Immediates
- Handling I/O
- Event Loop Best Practices
- New changes to timers and microtasks in Node v11
In this article, I’m going to explain how NodeJS internally glue a bunch of
For you to clearly understand how it works, I have included few code snippets in this post from the NodeJS codebase. Please note that I have truncated certain parts of those for brevity and have added some comments and log lines myself. Also, I would like you to understand that NodeJS codebase is very active and is subject to frequent code refactoring. Therefore, these code snippets might be drastically different across revisions. However, the overall functionality will be almost similar. Although these snippets would be gone in future releases, understanding how they work right now could make it really easy for you to understand the entire NodeJS codebase.
In the Event Loop series, I explained to you how it works, different phases of it and the sequence. But the event loop is implemented in libuv and nowhere in its code is mentioned about
process.nextTick. Now the weird question is….Where is
process.nextTick callbacks are called? The only thing we know is that they are called at the start and between each phase of the event loop.
I presume you have a sound understanding of the different phases of the event loop, microtasks,
process.nextTickand other terminology related to Node. If you haven’t I do appreciate if you can read the aforementioned Event Loop article series before reading this article as you might feel it difficult to follow some of the concepts mentioned here.
First of all, let’s see how
During the initialization of NodeJS runtime, an instance of
V8 environment is created and the environment is started by calling
Environment::Start method. In this method,
SetupProcessObject is executed which makes some interesting
As you can see
_setupNextTick function is set in the
process object during the initialization of
_setupNextTick is a reference to the
SetupNextTick function in
C++ context. Following is the definition of the
I will explain how this
SetupNextTick function comes into play in a while.
V8 environment, two JS scripts called
bootstrappers are run.
Node bootstrapper calls its
startup function during execution.
During the execution of
startup() function, NodeJS
requires the next tick module from
internal/process/next_tick and executes its
setup() export function.
setup() function is exported from
next_tick.js and is a reference to the function
setupNextTick defined in the same file.
- Upon calling,
setupNextTickfunction sets the attribute
processobject (line 22) as a reference to the
nextTickfunction defined in the same scope. This is how
process.nextTickcan be called from userland.
nextTickfunction (line 37) merely adds a given callback into a queue.
- There’s another function called
_tickCallback(line 27) defined in the same scope which is where the
nextTickqueue is processed. For ease of reading, I extracted it out as a separate gist as follows. Let’s read it carefully.
_tickCallback()function is called, it will iterate through the queue where
nextTickcallbacks are queued and will execute each and every callback until there are no callbacks left in the queue (line 4, inner
- Then, the
_tickCallback()function will call
runMicrotasks()function (line 21). This function will process the microtasks queue (e.g, callbacks of resolved/rejected promises). It’s possible that new
nextTickcallbacks are added while executing microtasks (e.g, call of
process.nextTickin promise resolve/reject callback).
- Above step1 and step2 is repeated until no more callbacks left in the
nextTickqueue (line 3, outer
The golden point is….. You need to trigger
**nextTick** queue and the microtasks queue.
To do this,
_tickCallback function should be passed somehow to
_tickCallback function is referenced in
C++ context by calling
setupNextTick. Upon the execution of
process._setupNextTick, it’s passed
tickCallback as the only parameter (see the
next_tick.js gist above).
Now if you remember I explain before,
process._setupNextTick is actually a
V8 environment. For clarity, I’ll just copy/paste the gist again (It’s time to scroll up if you don’t remember ;))
C++ function to
process._setupNextTick JS function is
SetupNextTick which is defined in
This method will call
set_tick_callback_function with the first parameter provided. Therefore, whatever you pass as the first parameter to
process._setupNextTick is passed to
set_tick_callback_function. Now go up and check what we call
setupNextTick JS definition.
Wow!! Eventually, the reference to
_tickCallback function is passed to
set_tick_callback_function will set the reference to the
_tickCallback function as
tick_callback_function in the
V8 environment. The conclusion is, calling
_tickCallback function which subsequently processes the
nextTick queue and the microtask queue.
Now if you recall what I mentioned above….
The golden point is….. You need to trigger
**nextTick**queue and the microtasks queue.
You know how ;)
Now we need to know where
tick_callback_function is called in
C++ context. Let’s go back to the event loop now.
C? The answer is, this binding glue is the
C++ bindings for libuv. Those are the functions written in
C++ who bridges
When you are adding multiple timers using
C++, and then
C++ to libuv(
timer_cb attached to a handle which is invoked by libuv. Essentially, calling the
timer_cb of a handle in libuv will trigger the execution of the multiple timers which were set at the same time with the same timeout value (I know you have questions now, but just keep them in mind for my next post on NodeJS timers in detail ;))
OnTimeout function in
C++ is called, NodeJS will cross the
JS Boundary, an interesting function is called named
Now if you search for
MakeCallback, you will find multiple overloaded
MakeCallback functions in
async_wrap.cc with different signatures:
At the time of this writing:
Each of them is written for very distinct purpose but if you look closely, you’ll see that they all eventually calls the
InternalMakeCallback function defined in
MakeCallback is called, it will pass the appropriate JS callback which needs to be called by the
InternalMakeCallback as the 3rd parameter (
callback) of the
**callback** is the almighty who processes a single phase of the event loop.
Then comes the most important part. You’ll see that after calling the provided
scope.Close() function is called.
Let’s look at the definition of
scope.Close() function defined in
Within this function, it executes the
Do you remember what
tick_callback_function is? It’s the same
C++ context which indicates that every time
InternalMakeCallback is called from
_tickCallback function is called at the end. Therefore, at the end of each phase,
nextTick queue and the microtask queue are processed.
But, If you read my Event Loop article series or if you have personally experienced
process.nextTick callbacks and the microtasks are run even before the timers phase start. How is this happen?
This is because the Node Bootstrapper which I mentioned above also loads the CommonJS module loader internally. During the bootstrap of CommonJS module loader, it manually triggers
_tickCallback to process any
nextTick callbacks added at the start.
PS: For the above examples, I referred to the NodeJS source at revision b267d2aae6. Due to many refactoring processes, the above example codes might be different across different NodeJS versions.
I suppose you now know how the mysterious
process.nextTick works. If you have any questions regarding anything I’ve mentioned or if you like to add something please feel free to comment. Thanks.
Background Image Courtesy: https://www.hdwallpapers.in/walls/earth_horizon-wide.jpg