Node.js Architecture - How does Node.js handle asynchronous tasks?

Node.js Architecture - How does Node.js handle asynchronous tasks?

Daily short news for you
  • Continuing to update on the lawsuit between the Deno group and Oracle over the name JavaScript: It seems that Deno is at a disadvantage as the court has dismissed the Deno group's complaint. However, in August, they (Oracle) must be held accountable for each reason, acknowledging or denying the allegations presented by the Deno group in the lawsuit.

    JavaScript™ Trademark Update

    » Read more
  • This time last year, I was probably busy running. This year, I'm overwhelmed with work and have lost interest. But sitting too much has made my belly grow, getting all bloated and gaining weight. Well, I’ll just try to walk every day to relax my muscles and mind a bit 😮‍💨

    The goal is over 8k steps 👌

    » Read more
  • Just a small change on the Node.js homepage has stirred the community. Specifically, when you visit the homepage nodejs.org, you will see a button "Get security support for Node.js 18 and below" right below the "Download" button. What’s notable is that it leads to an external website outside of Node.js, discussing a service that provides security solutions for older Node.js versions, which no longer receive security updates. It even stands out more than the Download button.

    The community has condemned this action, stating that it feels a bit "excessive," and suggested consulting them before making such decisions. On the Node side, they argue that this is appropriate as it is from a very significant sponsoring partner. As of now, the link still exists. Let's wait to see what happens next.

    » Read more

Problem

In the previous article, we learned that JavaScript/Node.js has only one main thread to execute JavaScript code. We also understood the difference between synchronous and asynchronous I/O tasks. JavaScript/Node.js smartly adopts an asynchronous handling mechanism to avoid blocking the call stack. In today's article, we will explore how Node.js handles asynchronous operations.

First, let’s get acquainted with a new component: Libuv.

Libuv

Let’s revisit the example from the previous section.

fs.readFile(file.pdf)
  .then(pdf => console.log("pdf size", pdf.size));

fs.readFile(file.doc)
  .then(doc => console.log("doc size", doc.size));

The order in which functions are pushed onto the call stack is as follows.

+------------------------------+
|                              |
| fs.readFile(file.pdf)        |
+------------------------------+
|        Call stack            |
|------------------------------|
             |
             v
+------------------------------+
|                              |
| fs.readFile(file.doc)        |
+------------------------------+
|        Call stack            |
|------------------------------|

Since the results of asynchronous functions are not returned immediately, the call stack executes the two commands above very quickly. So, where and how are the results of these two functions handled? In other words, where does the code inside then go?

Libuv is where asynchronous I/O is handled. Libuv is an external library chosen by Node.js to handle I/O. JavaScript in browsers does not use Libuv but implements something called Web APIs.

When the call stack encounters asynchronous functions, it immediately offloads them to libuv or Web APIs, so it barely does any processing here. Once the asynchronous function has a result, libuv attaches the result to a callback function, as well as the function inside then (note that the callback or then is always a function with a parameter, which is the result returned by libuv), and pushes it into a place called the Callback Queue.

So, what happens to the Callback Queue after libuv has the result?

Event loop

The Event Loop is an essential component of JavaScript/Node.js. It is an infinite loop that never stops while the program is running. The Event Loop's job is to bring callback functions from the Callback Queue back to the call stack.

The Event Loop always monitors the call stack because it only starts transferring callback functions from the Callback Queue to the call stack when the call stack is empty. This is also the reason why the example in the first section prints World before Hello.

setTimeout(function() {
  console.log("Hello");
}, 0);

console.log("World");

setTimeout with a value of 0 has almost no delay, so why is Hello not printed before World? It’s simple: the callback function inside setTimeout was pushed out of the call stack to libuv or Web APIs before the Event Loop could bring it back to the call stack.

The Event Loop has only one job: to bring callback functions from the Callback Queue back to the call stack. It does not directly execute JavaScript code. Code is only executed when it is placed in the call stack. If the call stack is not empty, the functions in the Callback Queue are never brought back to the call stack. This is why it is often said never to block the Event Loop. If blocked, the program becomes sluggish because requests will never have their results returned to the user.

Diagram of Node.js asynchronous handling process

Thanks to the presence of critical components like libuv, Web APIs, the Callback Queue, and the Event Loop, Node.js can process the results of asynchronous functions. This process can be summarized in the following diagram.

+------------------------------+
|         Call Stack           |
|----------------------------- |
| Encounter an asynchronous    |
| function                     |
|                              |
|                              |
| -> Push to libuv/Web APIs    |
+------------------------------+
             |
             v
+------------------------------+
|      libuv/Web APIs          |
|----------------------------- |
| Process I/O                  |
| Once done, push callback     |
| into Callback Queue          |
+------------------------------+
             |
             v
+------------------------------+
|        Callback Queue        |
|----------------------------- |
| [I/O result and callback]    |
|                              |
+------------------------------+
             |
             v
+------------------------------+
|          Event Loop          |
|----------------------------- |
| Check: Is Call Stack empty?  |
| If empty, push callback from |
| Callback Queue to Call Stack |
+------------------------------+
             |
             v
+------------------------------+
|         Call Stack           |
|----------------------------- |
| Execute callback function    |
+------------------------------+

Conclusion

With its single-threaded architecture, Node.js has chosen an asynchronous handling mechanism to optimize performance and prevent the call stack from being blocked by I/O tasks. This mechanism relies on the collaboration of critical components: libuv (a library for asynchronous I/O handling) or Web APIs, the Callback Queue (a queue containing callback functions after I/O completion), and the Event Loop (a loop responsible for bringing callbacks back to the call stack when it is empty). When an asynchronous task is called, Node.js offloads it to libuv or Web APIs for processing. After completion, the result and callback function are placed into the Callback Queue, waiting for the Event Loop to push them onto the call stack for execution. Thanks to this seamless operation, Node.js achieves efficient handling of asynchronous tasks without interrupting the main thread.

In summary, the strength of Node.js in handling asynchronous operations lies in the combination of libuv, the Callback Queue, and the Event Loop. This allows Node.js to maintain high performance but also requires developers to be cautious not to block the Event Loop, which could hinder program optimization. Understanding this mechanism enables us to leverage Node.js more effectively, especially when working with complex I/O tasks.

In the next article, let’s delve deeper into the Event Loop.

Premium
Hello

5 profound lessons

Every product comes with stories. The success of others is an inspiration for many to follow. 5 lessons learned have changed me forever. How about you? Click now!

Every product comes with stories. The success of others is an inspiration for many to follow. 5 lessons learned have changed me forever. How about you? Click now!

View all

Subscribe to receive new article notifications

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.

Comments (0)

Leave a comment...