Handling Async Operations in Node.js

Prachi
Prachi
Nov 5 · 7 min read
Image for post
Image for post
Photo by Clay Banks on Unsplash

After a lot of digging, I understood how node.js internally works. This blog is based on my researches, stay patient to deep dive!😁

Okay! So let’s understand first what Node.js is 🐘. The documentation says: “Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.”

A lot of fancy words here 🤯. Let’s together break it down 💪 First thing first, what is runtime and why node.js is a runtime ???

A runtime is any code that is executed to make your code work. The code that we write some code that will be executed by the runtime. A runtime can consist of a compiler, an interpreter, an engine to execute your code, thread pool to provide threads for your code’s process.

An engine is the actual code responsible to execute your program. It converts your code into machine code so that your computer(CPU) will execute it.

In JavaScript — The JavaScript engine translates your script into runnable machine code instructions so it can be executed by the CPU.

JavaScript Runtime Environment is just like a big container that contains other small containers like engine, heap, stack, web API, event loop, callback queue. As the JS engine parses the code it starts putting pieces of it into different containers.

The important thing is the JavaScript engine implementation is totally independent of the runtime environment. Engines aren’t developed with any particular environment in mind.

For example — The Chrome Browser and node.js use the same Engine — V8, but their Runtimes are different: in Chrome you have the window, DOM objects etc, while node gives you Buffers and processes.

Imagine a robot is cooking food:

  • Your code would be the instructions to the robot to cook food.
  • The engine would be the robot that can understand the instructions and act on it.
  • The runtime would be the LPG gas stove and the utensils.

Node.js is a runtime environment because it has V8 engine, libuv threadpool and OS Async Helpers. All of these gives you the power to write the JavaScript code on the server.

The documentation says: “V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.”

Well, it means, V8 is a C++ program, which receives JavaScript code, compiles, and executes it.V8’s responsibilities:

  1. Compiles and executes JS code.
  2. Handling call stack to run your JS functions in some order
  3. Managing memory allocation for objects — the memory heap
  4. Garbage collection of objects which are no longer in use
  5. Provide all the data types, operators, objects, and functions

Note:-V8 doesn't know anything about the Document Object Model (DOM) — which is provided by the browser

Anything your computer does is done using a process. For example, a file is opened by a text editor process. Music is played by a music player process.

Similarly, in order to run your node program, a process is required. This process is created as soon as you run node in your terminal.

Each process has threads which actually execute the code inside the program of the process. Threads use your CPU cores to execute the code. A process can have more than one thread.

So, when you run the following code using node: const mul= 4*2; — A process is created and threads are assigned to the process to execute the code.

NodeJS process is not “single-threaded”. “Event loop” is single-threaded

Event Loop

console.log("This is the first statement");setTimeout(function(){
console.log("This is the second statement");
}, 5000);
console.log("This is the third statement");

Output

This is the first statement
This is the third statement
This is the second statement

Node.js doesn’t wait for 5 sec for the second statement, rather it executes the third statement and in parallel waits for the timer of 5 sec to expire despite being single threaded. How? Event Loop to the rescue!

“Event loop” is, well, just a loop like a for loop or a while loop. When you start your node process by running node app.js, the following steps take place:

  1. V8 engine executes your code in app.js
  2. V8 engine immediately starts “Event Loop”

So one thing should be very much clear now — Event loop doesn’t execute your code , V8 engine is responsible or executing your code.

Wait! Hold On! Which tasks and what is the “Job Queue” ?

So a Job Queue is FIFO data-structure. The tasks which are pushed first will be picked up first by the Job Queue.

The tasks here is any code inside the function that needs to be executed. When I/O operation, setTimeout, setInterval, setImmediate, or an OS task completes, callback function is called, which is then entered inside the Job Queue. It will ‘wait’ until the Stack is completely empty. When the Stack is empty it will send the callback function at the beginning of the queue to the Stack.

As I mentioned above, event loop is like a for or while loop. It has certain phases through which it iterates — it is called “Event Loop Iteration”.

Each of the phases has its own queue/heap which is used by the event loop to push/store the callbacks to be executed (There is a misconception in Node.js that there is only a single global queue where the callbacks are queued for execution which is not true.).

Phases are:

1. Timer

Technically, the execution of the timer callbacks is controlled by the Poll phase of the event loop (we will see that later in this article).

2. Pending Callbacks

3. Idle/Prepare

4. Poll Phase

The poll phase has two main functions:

  1. Calculating how long it should block and poll for I/O, then
  2. Processing events in the poll queue.

When the event loop enters the poll phase and there are no timers(setTimeout, setInterval) scheduled, one of two things will happen:

  • If the poll queue is not empty, the event loop will iterate through its queue of callbacks executing them synchronously until either the queue has been exhausted, or the system-dependent hard limit is reached.
  • If the poll queue is empty, one of two more things will happen:
  • If scripts have been scheduled by setImmediate(), the event loop will end the poll phase and continue to the check phase to execute those scheduled scripts.
  • If scripts have not been scheduled by setImmediate(), the event loop will wait for callbacks to be added to the queue, then execute them immediately.

Once the poll queue is empty the event loop will check for timers whose time thresholds have been reached. If one or more timers are ready, the event loop will wrap back to the timers phase to execute those timers’ callbacks.

5. Check/setImmediate

6. Closing callbacks

“libuv” is a library that provides a set of threads(thread pool) to NodeJS runtime to execute long-running tasks, like fs module-based tasks. By default, it provides 4 threads to each node process but you can change the thread pool size by setting process.env.UV_THREADPOOL_SIZE to any value you want.

“OS Async Helpers” are used when any low-level OS operations take place. For example, a REST API call using http or https module or creation of a web server using http.createServer(). These operations never use the thread pool and are executed immediately as soon as a CPU core is available for operation.

Once “libuv” or “OS Async Helpers” are done with their tasks, they put the callbacks into the “Job Queue” for the “Event Loop” to pick them up and then send them to “V8 Engine” to be executed.

Example

Image for post
Image for post

Output

2
1

Let’s see how Node.js executes this code:

  1. When main() is called, fs.readfile() is encountered first with a callback. It’s callback is pushed inside the I/O phase queue because fs operations are I/O operation.
  2. When event loop sees that the file reading operation is complete, it starts executing the callback, after which, it moves to the Check(setImmediate) phase.

Check phase comes before the Timers phase .Hence, when in I/O phase, the callback of setImmediate will always run before setTimeout(fn, 0).

The main advantage to using setImmediate() over setTimeout() is setImmediate() will always be executed before any timers if scheduled within an I/O cycle, independently of how many timers are present.

Thank you for reading! You can follow me on Medium and LinkedIn. Feel free to ask any questions in the comments.🌼🌼🌼

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store