Node.js: que es, cuando y como usarlo, y por que debería

Probablemente hayas leído estas oraciones antes ...

Node.js es un tiempo de ejecución de JavaScript construido en el motor de JavaScript V8 de Chrome.

… Y se quedaron preguntándose qué significaba todo esto. Con suerte, al final de este artículo comprenderá mejor estos términos, así como qué es Node, cómo funciona y por qué y cuándo es una buena idea usarlo.

Comencemos repasando la terminología.

E / S (entrada / salida)

Abreviatura de entrada / salida, E / S se refiere principalmente a la interacción del programa con el disco y la red del sistema. Los ejemplos de operaciones de E / S incluyen leer / escribir datos desde / hacia un disco, realizar solicitudes HTTP y comunicarse con bases de datos. Son muy lentos en comparación con el acceso a la memoria (RAM) o el trabajo en la CPU.

Sincrónico vs asincrónico

La ejecución sincrónica (o sincronizada) generalmente se refiere a la ejecución de código en secuencia. En la programación sincronizada, el programa se ejecuta línea por línea, una línea a la vez. Cada vez que se llama a una función, la ejecución del programa espera hasta que esa función regresa antes de continuar con la siguiente línea de código.

La ejecución asincrónica (o asincrónica) se refiere a la ejecución que no se ejecuta en la secuencia que aparece en el código. En la programación asíncrona, el programa no espera a que se complete la tarea y puede pasar a la siguiente.

En el siguiente ejemplo, la operación de sincronización hace que las alertas se activen en secuencia. En la operación asincrónica, mientras que alert (2) parece ejecutarse en segundo lugar, no es así.

// Synchronous: 1,2,3 alert(1); alert(2); alert(3); // Asynchronous: 1,3,2 alert(1); setTimeout(() => alert(2), 0); alert(3);

Una operación asincrónica a menudo está relacionada con E / S, aunque setTimeoutes un ejemplo de algo que no es E / S pero sigue siendo asincrónico. En términos generales, todo lo relacionado con la computación está sincronizado y todo lo relacionado con la entrada / salida / tiempo es asíncrono. La razón por la que las operaciones de E / S se realizan de forma asincrónica es que son muy lentas y, de lo contrario, bloquearían la ejecución del código.

Bloqueo vs no bloqueo

El bloqueo se refiere a las operaciones que bloquean la ejecución adicional hasta que finaliza la operación, mientras que el no bloqueo se refiere al código que no bloquea la ejecución. O, como dice la documentación de Node.js, el bloqueo es cuando la ejecución de JavaScript adicional en el proceso de Node.js debe esperar hasta que se complete una operación que no es de JavaScript.

Los métodos de bloqueo se ejecutan de forma sincrónica mientras que los métodos sin bloqueo se ejecutan de forma asincrónica.

// Blocking const fs = require('fs'); const data = fs.readFileSync('/file.md'); // blocks here until file is read console.log(data); moreWork(); // will run after console.log // Non-blocking const fs = require('fs'); fs.readFile('/file.md', (err, data) => { if (err) throw err; console.log(data); }); moreWork(); // will run before console.log

En el primer ejemplo anterior, console.logse llamará antes moreWork(). En el segundo ejemplo fs.readFile()es sin bloqueo, por lo que la ejecución de JavaScript puede continuar y moreWork()se llamará primero.

En Node, el no bloqueo se refiere principalmente a las operaciones de E / S, y JavaScript que presenta un rendimiento deficiente debido a un uso intensivo de la CPU en lugar de esperar a una operación que no es JavaScript, como la E / S, no se suele denominar bloqueo.

Todos los métodos de E / S de la biblioteca estándar de Node.js proporcionan versiones asíncronas, que no bloquean y aceptan funciones de devolución de llamada. Algunos métodos también tienen contrapartes de bloqueo, que tienen nombres que terminan con Sync.

Las operaciones de E / S sin bloqueo permiten que un solo proceso atienda varias solicitudes al mismo tiempo. En lugar de bloquear el proceso y esperar a que se completen las operaciones de E / S, las operaciones de E / S se delegan al sistema, de modo que el proceso pueda ejecutar el siguiente fragmento de código. Las operaciones de E / S sin bloqueo proporcionan una función de devolución de llamada que se llama cuando se completa la operación.

Devoluciones de llamada

Una devolución de llamada es una función que se pasa como argumento a otra función, que luego se puede invocar (volver a llamar) dentro de la función externa para completar algún tipo de acción en un momento conveniente. La invocación puede ser inmediata (devolución de llamada sincronizada) o puede suceder en un momento posterior (devolución de llamada asíncrona).

// Sync callback function greetings(callback) { callback(); } greetings(() => { console.log('Hi'); }); moreWork(); // will run after console.log // Async callback const fs = require('fs'); fs.readFile('/file.md', function callback(err, data) { // fs.readFile is an async method provided by Node if (err) throw err; console.log(data); }); moreWork(); // will run before console.log 

En el primer ejemplo, la función de devolución de llamada se llama inmediatamente dentro de la función de saludos externos y se registra en la consola antes de continuar moreWork().

En el segundo ejemplo, fs.readFile (un método asíncrono proporcionado por Node) lee el archivo y cuando termina llama a la función de devolución de llamada con un error o el contenido del archivo. Mientras tanto, el programa puede continuar con la ejecución del código.

Se puede llamar a una devolución de llamada asincrónica cuando ocurre un evento o cuando se completa una tarea. Evita el bloqueo al permitir que se ejecute otro código mientras tanto.

En lugar de que el código se lea de arriba a abajo de forma procedimental, los programas asíncronos pueden ejecutar diferentes funciones en diferentes momentos en función del orden y la velocidad en que ocurren las funciones anteriores, como las solicitudes http o las lecturas del sistema de archivos. Se utilizan cuando no sabe cuándo se completará alguna operación asincrónica.

Debe evitar el " infierno de la devolución de llamada ", una situación en la que las devoluciones de llamada se anidan dentro de otras devoluciones de llamada a varios niveles de profundidad, lo que hace que el código sea difícil de entender, mantener y depurar.

Eventos y programación impulsada por eventos

Los eventos son acciones generadas por el usuario o el sistema, como un clic, una descarga completa de un archivo o un error de hardware o software.

La programación impulsada por eventos es un paradigma de programación en el que el flujo del programa está determinado por eventos. Un programa impulsado por eventos realiza acciones en respuesta a eventos. Cuando ocurre un evento, activa una función de devolución de llamada.

Ahora, intentemos comprender Node y veamos cómo se relacionan con él.

Node.js: ¿que es, por que se creó y como funciona?

En pocas palabras, Node.js es una plataforma que ejecuta programas JavaScript del lado del servidor que pueden comunicarse con fuentes de E / S como redes y sistemas de archivos.

Cuando Ryan Dahl creó Node en 2009, argumentó que la E / S se estaba manejando incorrectamente, bloqueando todo el proceso debido a la programación sincrónica.

Las técnicas tradicionales de servicio web utilizan el modelo de hilo, es decir, un hilo para cada solicitud. Dado que en una operación de E / S la solicitud pasa la mayor parte del tiempo esperando a que se complete, los escenarios de E / S intensivos implican una gran cantidad de recursos no utilizados (como memoria) vinculados a estos subprocesos. Por lo tanto, el modelo de "un hilo por solicitud" para un servidor no se escala bien.

Dahl argued that software should be able to multi-task and proposed eliminating the time spent waiting for I/O results to come back. Instead of the thread model, he said the right way to handle several concurrent connections was to have a single-thread, an event loop and non-blocking I/Os. For example, when you make a query to a database, instead of waiting for the response you give it a callback so your execution can run through that statement and continue doing other things. When the results come back you can execute the callback.

The event loop is what allows Node.js to perform non-blocking I/O operations despite the fact that JavaScript is single-threaded. The loop, which runs on the same thread as the JavaScript code, grabs a task from the code and executes it. If the task is async or an I/O operation the loop offloads it to the system kernel, like in the case for new connections to the server, or to a thread pool, like file system related operations. The loop then grabs the next task and executes it.

Since most modern kernels are multi-threaded, they can handle multiple operations executing in the background. When one of these operations completes (this is an event), the kernel tells Node.js so that the appropriate callback (the one that depended on the operation completing) may be added to the poll queue to eventually be executed.

Node keeps track of unfinished async operations, and the event loop keeps looping to check if they are finished until all of them are.

To accommodate the single-threaded event loop, Node.js uses the libuv library, which, in turn, uses a fixed-sized thread pool that handles the execution of some of the non-blocking asynchronous I/O operations in parallel. The main thread call functions post tasks to the shared task queue, which threads in the thread pool pull and execute.

Inherently non-blocking system functions such as networking translate to kernel-side non-blocking sockets, while inherently blocking system functions such as file I/O run in a blocking way on their own threads. When a thread in the thread pool completes a task, it informs the main thread of this, which in turn, wakes up and executes the registered callback.

The above image is taken from Philip Roberts’ presentation at JSConf EU: What the heck is the event loop anyway? I recommend watching the full video to get a high level idea about how the event loop works.

The diagram explains how the event loop works with the browser but it looks basically identical for Node. Instead of web APIs we would have Node APIs.

According to the presentation, the call stack (aka execution stack or “the stack”) is a data structure which records where in the program we are. If we step into a function, we put something onto the stack. If we return from a function, we pop it off the top of the stack.

This is how the code in the diagram is processed when we run it:

  1. Push main() onto the stack (the file itself)
  2. Push console.log(‘Hi’); onto the stack, which executes immediately logging “Hi” to the console and gets popped off the stack
  3. Push setTimeout(cb, 5000) onto the stack. setTimeout is an API provided by the browser (on the backend it would be a Node API). When setTimeout is called with the callback function and delay arguments, the browser kicks off a timer with the delay time
  4. The setTimeout call is completed and gets popped off the stack
  5. Push console.log(‘JSConfEU’); onto the stack, which executes immediately logging “JSConfEU” to the console and gets popped off the stack
  6. main() gets popped off the stack
  7. After 5000 milliseconds the API timer completes and the callback gets moved to the task queue
  8. The event loop checks if the stack is empty because JavaScript, being single-threaded, can only do one thing at a time (setTimeout is not a guaranteed but a minimum time to execution). If the stack is empty it takes the first thing on the queue and pushes it onto the stack. Therefore the loop pushes the callback onto the stack
  9. The callback gets executed, logs “there” to the console and gets popped off the stack. And we are done

If you want to go even deeper into the details on how Node.js, libuv, the event loop and the thread pool work, I suggest checking the resources on the reference section at the end, in particular this, this and this along with the Node docs.

Node.js: why and where to use it?

Since almost no function in Node directly performs I/O, the process never blocks (I/O operations are offloaded and executed asynchronously in the system), making it a good choice to develop highly scalable systems.

Due to its event-driven, single-threaded event loop and asynchronous non-blocking I/O model, Node.js performs best on intense I/O applications requiring speed and scalability with lots of concurrent connections, like video & audio streaming, real-time apps, live chats, gaming apps, collaboration tools, or stock exchange software.

Node.js may not be the right choice for CPU intensive operations. Instead the traditional thread model may perform better.

npm

npm is the default package manager for Node.js and it gets installed into the system when Node.js is installed. It can manage packages that are local dependencies of a particular project, as well as globally-installed JavaScript tools.

www.npmjs.com hosts thousands of free libraries to download and use in your program to make development faster and more efficient. However, since anybody can create libraries and there’s no vetting process for submission, you have to be careful about low quality, insecure, or malicious ones. npm relies on user reports to take down packages if they violate policies, and to help you decide, it includes statistics like number of downloads and number of depending packages.

How to run code in Node.js

Start by installing Node on your computer if you don’t have it already. The easiest way is to visit nodejs.org and click to download it. Unless you want or need to have access to the latest features, download the LTS (Long Term Support) version for you operating system.

You run a Node application from your computer’s terminal. For example make a file “app.js” and add console.log(‘Hi’); to it. On your terminal change the directory to the folder where this file belongs to and run node app.js. It will log “Hi” to the console. ?

References

Here are some of the interesting resources I reviewed during the writing of the article.

Node.js presentations by its author:

  • Original Node.js presentation by Ryan Dahl at JSConf 2009
  • 10 Things I Regret About Node.js by Ryan Dahl at JSConf EU 2018

Node, the event loop and the libuv library presentations:

  • What the heck is the event loop anyway? by Philip Roberts at JSConf EU
  • Node.js Explained by Jeff Kunkle
  • In The Loop by Jake Archibald at JSConf Asia 2018
  • Everything You Need to Know About Node.js Event Loop by Bert Belder
  • A deep dive into libuv by Saul Ibarra Coretge at NodeConf EU 2016

Node documents:

  • About Node.js
  • The Node.js Event Loop, Timers, and process.nextTick()
  • Overview of Blocking vs Non-Blocking

Additional resources:

  • Art of Node by Max Ogden
  • Callback hell by Max Ogden
  • What is non-blocking or asynchronous I/O in Node.js? on Stack Overflow
  • Event driven programming on Wikipedia
  • Node.js on Wikipedia
  • Thread on Wikipedia
  • libuv

Thanks for reading.