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
  • Before, I mentioned that github/gh-copilot is a command-line tool that suggests commands or explains them. While browsing through some "comments," I discovered a rather interesting way of using it that I hadn't expected. That is to set an alias for the command gh copilot suggest as "??" and "???" and then, after getting used to it, it becomes extremely convenient. For example, if you want to compress a directory into a tar.gz format but forget the syntax, you can simply do: bash $ ?? "compress the pages directory into .tar.gz" Or if you encounter a command that is hard to understand, you can use: bash $ ??? "tar -xzvf pages.tar.gz pages"

    » Read more
  • A very nice website that aggregates CLI command line tools that I stumbled upon: terminaltrove.com

    It has a ranking system and is categorized, making it very easy to explore by topics that interest you, feel free to study it 😄

    » Read more
  • These past few days, I've been redesigning the interface for the note-taking app OpenNotas. It's quite strange to think about why I chose DaisyUI back then 😩

    » 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

Me & the desire to "play with words"

Have you tried writing? And then failed or not satisfied? At 2coffee.dev we have had a hard time with writing. Don't be discouraged, because now we have a way to help you. Click to become a member now!

Have you tried writing? And then failed or not satisfied? At 2coffee.dev we have had a hard time with writing. Don't be discouraged, because now we have a way to help you. Click to become a member 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...