Node.js was created in 2009 by Ryan Dahl, inspired by Flickr’s file upload progress bar, where he realized that the browser didn’t know how much of the file was uploaded and had to consult a web server to get this information.
Node.js came with a single thread proposal, where only one thread takes care of requests and each one is treated as an event. Unlike other languages such as PHP, Java, and Ruby, which are multi-thread, where every request received is created a new thread, which often ends up consuming computer resources unnecessarily.
This model may seem strange at first because we always hear that having something multi-thread has better performance. But even though Node.js is a single thread, we are able to handle requests concurrently. Using non-blocking I/O calls and asynchronous API, considered a strong feature within Node.js, not blocking the execution of resources facilitates parallel execution and makes better use of computational resources.
The key that makes Node.js so efficient is the Event Loop concept, with great responsibility it is basically an infinite loop that at each iteration checks if we have events to be executed in the Task Queue.
In Task Queue he takes the event and sends it to be executed in the Call Stack. Its performance is due to the way it is executed, in a non-blocking way.
In Computer Science, a stack is a data structure that allows the removal of elements and insertion of new objects. It works using the LIFO (Last in First Out) concept. Its basic operations are push and pop, a stack must keep its maximum size in a variable, and when that size is exceeded we have one of the famous errors in the programming, stack overflow.
It is the memory available to the Node.js process, whenever we call a “new Object” or create a variable to store a value, it is the responsibility of the Heap to store the values.
It can be considered as a queue of messages to be processed when we have calls such as setTimeout, access to network modules, I / O, among others.
The Event loop checks if it is a task that will require heavier processing if it does, it sends these executions to libuv, which executes them in a separate thread pool, allowing V8 to continue executing the code on the main thread.
However, if it is a lighter process it sends to be processed in the call stack.
Um exemplo de função que tem um processamento mais pesado é o readFile do módulo fs do Node.js, a libuv administra essa função em outra thread, quando ela terminar de ser
An example of a function that has heavier processing is the readFile of the fs module.
Libuv manages this function in another thread. When it finishes executing the callback will be added to the Task Queue to be executed in the call stack as soon as it is available.
The responsible for analyzing if my call stack is empty and if there are tasks in the Task Queue to be processed in the Call Stack is the Event Loop.
He is responsible for calling the next tasks while Node.js is running.
Macro e Micro Tasks
Up to this point, we understand how Call Stack works and how our functions are organized within the Task Queue.
But within the Task Queue, we have two types of tasks:
- Micro tasks: such as promises and process.nextTick;
- Macro tasks: where we have well-known examples in day-to-day development, such as setTimeout, setInterval.
In this article I tried to explain in a basic way the functioning of the modules of the Node.js virtual machine, I will write more content about Node in the future.