Tutorial

Cómo iniciar procesos secundarios en Node.js

Published on September 11, 2020
Español
Cómo iniciar procesos secundarios en Node.js

El autor seleccionó el COVID-19 Relief Fund para que reciba una donación como parte del programa Write for DOnations.

Introducción

Cuando un usuario ejecuta un único programa Node.js, lo ejecuta como un proceso de sistema operativo (SO) individual que representa la instancia del programa en ejecución. En ese proceso, Node.js ejecuta programas en un único hilo. Como se ha mencionado anteriormente en esta serie con el tutorial Cómo escribir código asíncrono en Node.js, debido a que solo un hilo puede ejecutarse sobre un proceso, las operaciones que tardan mucho tiempo en ejecutarse en JavaScript pueden bloquear el hilo de Node.js y retrasar la ejecución de otro código. Una estrategia clave para resolver este problema es iniciar un proceso secundario, o un proceso creado por otro proceso cuando nos enfrentamos a tareas de larga ejecución. Cuando se inicia un nuevo proceso, el sistema operativo puede emplear técnicas de multi procesamiento para garantizar que el proceso Node.js principal y el proceso secundario adicional se ejecutan de forma simultánea o al mismo tiempo.

Node.js incluye el módulo child_process que tiene funciones para crear nuevos procesos. Aparte de tratar con tareas de larga duración, este módulo también interactúa con el SO y ejecuta comandos shell. Los administradores de sistemas pueden usar Node.js para ejecutar comandos shell para estructurar y mantener sus operaciones como un módulo Node.js en vez de como secuencias de comando shell.

En este tutorial, creará procesos secundarios mientras ejecuta una serie de aplicaciones Node.js de muestra. Creará procesos con el módulo child_process recuperando los resultados de un proceso secundario a través de un búfer o cadena con la función exec(), y a continuación desde un flujo de datos con la función spawn(). Terminará usando fork() para crear un proceso secundario de otro programa Node.js con el que puede comunicarse cuando se ejecute. Para ilustrar estos conceptos, escribirá un programa para enumerar el contenido de un directorio, un programa para buscar archivos y un servidor web con múltiples endpoints.

Requisitos previos

Paso 1: Crear un proceso secundario con exec()

Los desarrolladores crean de forma habitual procesos secundarios para ejecutar comandos sobre su sistema operativo cuando necesitan manipular el resultado de sus programas Node.js con un shell, como usar una canalización o redireccionamiento shell. La función exec() en Node.js crea un nuevo proceso shell y ejecuta un comando en ese shell. El resultado del comando se mantiene en un búfer en la memoria, que puede aceptar a través de una función callback pasada a exec().

Vamos a comenzar a crear nuestros primeros procesos secundarios en Node.js. Primero, debemos configurar nuestro entornos de codificación para almacenar las secuencias de comandos que crearemos durante este tutorial. En el terminal, cree una carpeta llamada child-processes:

  1. mkdir child-processes

Introduzca esa carpeta en el terminal con el comando cd:

  1. cd child-processes

Cree un nuevo archivo llamado listFiles.js y abra el archivo en un editor de texto. En este tutorial usaremos nano, un editor de texto terminal:

  1. nano listFiles.js

Escribiremos un módulo Node.js que utiliza la función exec() para ejecutar el comando ls. El comando ls enumera los archivos y las carpetas en un directorio. Este programa toma el resultado del comando ls y lo muestra al usuario.

En el editor de texto, añada el siguiente código:

~/child-processes/listFiles.js
const { exec } = require('child_process');

exec('ls -lh', (error, stdout, stderr) => {
  if (error) {
    console.error(`error: ${error.message}`);
    return;
  }

  if (stderr) {
    console.error(`stderr: ${stderr}`);
    return;
  }

  console.log(`stdout:\n${stdout}`);
});

Primero, importamos el comando exec() desde el módulo child_process usando JavaScript destructuring. Una vez importado, usamos la función exec(). El primer argumento es el comando que nos gustaría ejecutar. En este caso, es ls -lh, que enumera todos los archivos y carpetas del directorio actual en formato largo, con un tamaño total de archivo en unidades legibles por el ser humano en la parte superior del resultado.

El segundo argumento es una función callback con tres parámetros: error, stdout y stderr. Si el comando no se ejecuta, error capturará el motivo por el que falló. Es posible que esto suceda si el shell no puede encontrar el comando que está intentando ejecutar. Si el comando se ejecutó correctamente, cualquier dato que escriba al flujo de resultado estándar se captura en stdout y cualquier dato que escriba al flujo error estándar se captura en stderr.

Nota: Es importante tener en cuenta la diferencia entre error y stderr. Si el comando en sí no se ejecuta, error capturará el error. Si el comando se ejecuta, pero devuelve el resultado al flujo de error, stderr lo capturará. Los programas más resilientes de Node.js gestionarán todos los resultados posibles para un proceso secundario.

En nuestra función callback, primero comprobaremos si recibimos un error. Si lo hicimos, mostramos el message (mensaje) del error (una propiedad del objeto Error) con console.error() y finalizamos la función con return. A continuación, comprobamos si el comando imprimió un mensaje de error y return si es así. Si el comando se ejecuta correctamente, registramos su resultado a la consola con console.log().

Vamos a ejecutar este archivo para verlo en acción. Primero, guarde y salga de nano pulsando CTRL+X.

De vuelta en su terminal, ejecute su aplicación con el comando node:

  1. node listFiles.js

Su terminal mostrará el siguiente resultado:

Output
stdout: total 4.0K -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js

Esto enumera el contenido del directorio child-processes en formato largo, junto con el tamaño del contenido en la parte superior. Sus resultados tendrán su propio usuario y grupo en lugar de sammy. Esto muestra que el programa listFiles.js ejecutó correctamente el comando shell ls -lh.

Ahora, vamos a ver otra forma de ejecutar procesos simultáneos. El módulo child_process de Node.js puede ejecutar también archivos ejecutables con la función execFile(). La diferencia principal entre las funciones execFile() y exec() es que el primer argumento de execFile() es ahora una ruta a un archivo ejecutable en vez de un comando. El resultado del archivo ejecutable se guarda en un búfer como exec(), al que accedemos a través de una función callback con los parámetros error, stdout y stderr.

Nota: Las secuencias de comandos en Windows como archivos .bat y .cmd no se pueden ejecutar con execFile() porque la función no crea un shell cuando se ejecuta el archivo. En Unix, Linux y macOS las secuencias de comandos ejecutables no siempre necesitan un shell para ejecutarse. Sin embargo, un equipo Windows necesita un shell para ejecutar secuencias de comandos. Para ejecutar archivos de secuencias de comandos en Windows, utilice exec(), ya que crea un nuevo shell. Alternativamente, puede usar spawn(), que usará más tarde en este Paso.

Sin embargo, observe que puede ejecutar archivos .exe en Windows correctamente usando execFile(). Esta limitación solo se aplica a archivos de secuencias de comandos que requieren un shell para ejecutarse.

Vamos a comenzar añadiendo una secuencia de comandos ejecutable para que execFile() se ejecute. Escribiremos una secuencia de comandos bash que descarga el logotipo de Node.js desde el sitio web de Node.js, y Base64 lo codifica para convertir sus datos en una cadena de caracteres ASCII.

Cree un nuevo archivo de secuencia de comando shell llamado processNodejsImage.sh:

  1. nano processNodejsImage.sh

Ahora escriba una secuencia de comandos para descargar la imagen y base64 para convertirla:

~/child-processes/processNodejsImage.sh
#!/bin/bash
curl -s https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg > nodejs-logo.svg
base64 nodejs-logo.svg

La primera instrucción es una instrucción shebang. Se usa en Unix, Linux y macOs cuando queremos especificar un shell para que ejecute nuestra secuencia de comandos. La segunda instrucción es un comando curl. La utilidad cURL, cuyo comando es curl, es una herramienta de línea de comandos que puede transferir datos a y desde un servidor. Usamos cURL para descargar el logotipo de Node.js desde el sitio web, y luego usamos redirection para guardar los datos descargados a un nuevo archivo nodejs-logo.svg. La última instrucción utiliza la utilidad base64 para codificar el archivo nodejs-logo.svg que descargamos con cURL. La secuencia de comandos da como resultado la cadena codificada en la consola.

Guarde y salga antes de continuar.

Para que nuestro programa Node ejecute la secuencia de comandos bash, tenemos que hacer que sea ejecutable. Para hacer esto, ejecute lo siguiente:

  1. chmod u+x processNodejsImage.sh

Esto le dará a su usuario actual el permiso para ejecutar el archivo.

Con nuestra secuencia de comandos en su lugar, podemos escribir un nuevo módulo Node.js para ejecutarlo. Esta secuencia de comandos usará execFile() para ejecutar la secuencia de comandos en un proceso secundario, detectando cualquier error y mostrando todos los resultados en la consola.

En su terminal, cree un nuevo archivo JavaScript llamado getNodejsImage.js:

  1. nano getNodejsImage.js

Escriba el siguiente código en el editor de texto:

~/child-processes/getNodejsImage.js
const { execFile } = require('child_process');

execFile(__dirname + '/processNodejsImage.sh', (error, stdout, stderr) => {
  if (error) {
    console.error(`error: ${error.message}`);
    return;
  }

  if (stderr) {
    console.error(`stderr: ${stderr}`);
    return;
  }

  console.log(`stdout:\n${stdout}`);
});

Usaremos la desestructuración de JavaScript para importar la función execFile() desde el módulo child_process. Luego usamos esa función, pasando la ruta de archivo como el primero nombre. __dirname contiene la ruta del directorio del módulo en el que está escrito. Node.js proporciona la variable __dirname a un módulo cuando el módulo se ejecuta. Al usar __dirname, nuestra secuencia de comandos siempre encontrará el archivo processNodejsImage.sh en diferentes sistemas operativos, sin importar dónde ejecutemos getNodejsImage.js. Tenga en cuenta que para nuestra configuración actual del proyecto, getNodejsImage.js y processNodejsImage.sh deben estar en la misma carpeta.

El segundo argumento es un callback con los parámetros error, stdout y stderr. Igual que en nuestro ejemplo anterior que usó exec(), comprobamos cada resultado posible del archivo de la secuencia de comandos y los registraremos en la consola.

En su editor de texto, guarde este archivo y salga del editor.

En su terminal, utilice node para ejecutar el módulo:

  1. node getNodejsImage.js

Ejecutar la secuencia de comandos producirá un resultado similar a este:

Output
stdout: PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge ...

Tenga en cuenta que hemos recortado el resultado en este artículo debido a su gran tamaño.

Antes de que base64 codifique la imagen, processNodejsImage.sh primero la descarga. Puede también verificar que descargó la imagen inspeccionando el directorio actual.

Ejecute listFiles.js para buscar la lista actualizada de archivos en nuestro directorio:

  1. node listFiles.js

La secuencia de comandos mostrará contenido similar al siguiente en el terminal:

Output
stdout: total 20K -rw-rw-r-- 1 sammy sammy 316 Jul 27 17:56 getNodejsImage.js -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js -rw-rw-r-- 1 sammy sammy 5.4K Jul 27 18:01 nodejs-logo.svg -rwxrw-r-- 1 sammy sammy 129 Jul 27 17:56 processNodejsImage.sh

Ahora hemos ejecutado correctamente processNodejsImage.sh como proceso secundario en Node.js usando la función execFile().

Las funciones exec() y execFile() pueden ejecutar comandos sobre el shell del sistema operativo en un proceso secundario Node.js. Node.js también proporciona otro método con funcionalidad similar, spawn(). La diferencia es que en vez de obtener el resultado de los comandos shell a la vez, los obtendremos en bloques a través de un flujo. En la siguiente sección usaremos el comando spawn() para crear un proceso secundario.

Paso 2: Crear un proceso secundario con spawn()

La función spawn() ejecuta un comando en un proceso. Esta función devuelve datos a través de la API del flujo. Por tanto, para obtener el resultado del proceso secundario, debemos escuchar los eventos del flujo.

Los flujos en Node.js son instancias de los emisores de eventos. Si desea obtener más información sobre la escucha de eventos y los fundamentos de interacción con los flujos, puede leer nuestra guía sobre Usar emisores de eventos en Node.js.

A menudo es una buena idea seleccionar spawn() en vez de exec() o execFile() cuando el comando que desea ejecutar puede dar como resultado una gran cantidad de datos. Con un búfer, como utilizan exec() y execFile(), todos los datos procesados se guardan en la memoria de la computadora. Para cantidades de datos más grandes, esto puede degradar el rendimiento del sistema. Con un flujo, los datos se procesan y transfieren en pequeños trozos. Por tanto, puede procesar una gran cantidad de datos sin usar demasiada memoria en un momento dado.

Vamos a ver cómo podemos usar spawn() para crear un proceso secundario. Escribiremos un nuevo módulo Node.js que crea un proceso secundario para ejecutar el comando find. Usaremos el comando find para enumerar todos los archivos en el directorio actual.

Cree un nuevo archivo llamado findFiles.js:

  1. nano findFiles.js

En su editor de texto, comience invocando el comando spawn():

~/child-processes/findFiles.js
const { spawn } = require('child_process');

const child = spawn('find', ['.']);

Primero importamos la función spawn() desde el módulo child_process. A continuación invocamos la función spawn() para crear un proceso secundario que ejecuta el comando find. Albergamos la referencia al proceso en la variable child, que utilizaremos para escuchar sus eventos transmitidos.

El primer argumento en spawn() es el comando a ejecutar, en este caso find. El segundo argumento es un array que contiene los argumentos para el comando ejecutado. En este caso, le estamos indicando a Node.js que ejecute el comando find con el argumento ., lo que hace que el comando encuentre todos los activos en el directorio actual. El comando equivalente en el terminal es find .

Con las funciones exec() y execFile(), escribimos los argumentos con el comando en una cadena. Sin embargo, con spawn(), todos los argumentos para los comandos deben introducirse en el array. Eso es porque spawn(), a diferencia de exec() y execFile(), no crea un nuevo shell antes de ejecutar un proceso. Para tener los comandos con sus argumentos en una cadena, necesita que Node.js cree un nuevo shell también.

Vamos a continuar nuestro módulo añadiendo oyentes para el resultado del comando. Añada las siguientes líneas resaltadas:

~/child-processes/findFiles.js
const { spawn } = require('child_process');

const child = spawn('find', ['.']);

child.stdout.on('data', data => {
  console.log(`stdout:\n${data}`);
});

child.stderr.on('data', data => {
  console.error(`stderr: ${data}`);
});

Los comandos pueden devolver datos en el flujo stdout o el flujo stderr, de forma que añadió oyentes para ambos. Puede añadir oyentes invocando el método on() de cada objeto de los flujos. El evento datos de los flujos nos proporciona el resultado de los comandos para ese flujo. Siempre que obtengamos datos sobre ese flujo, los registramos en la consola.

A continuación escuchamos los dos otros eventos: el evento error si el comando no se ejecuta o se interrumpe y el evento close para cuando el comando haya terminado la ejecución, cerrando así el flujo.

En el editor de texto, complete el módulo Node.js escribiendo las siguientes líneas resaltadas:

~/child-processes/findFiles.js
const { spawn } = require('child_process');

const child = spawn('find', ['.']);

child.stdout.on('data', (data) => {
  console.log(`stdout:\n${data}`);
});

child.stderr.on('data', (data) => {
  console.error(`stderr: ${data}`);
});

child.on('error', (error) => {
  console.error(`error: ${error.message}`);
});

child.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});

Para los eventos error y close, configura un oyente directamente en la variable child. Cuando escucha eventos error, si se produce uno, Node.js ofrece un objeto Error. En este caso, registra la propiedad message del error.

Cuando se escucha el evento close, Node.js proporciona el código de salida del comando. Un código de salida indica si el comando se ejecutó correctamente o no. Cuando un comando se ejecuta sin errores, devuelve el valor más bajo posible para un código de salida: 0. Cuando se ejecuta con un error, devuelve un código no cero.

El módulo se ha completado. Guarde y salga de nano con CTRL+X.

Ahora ejecute el código con el comando node:

  1. node findFiles.js

Una vez completado, encontrará el siguiente resultado:

Output
stdout: . ./findFiles.js ./listFiles.js ./nodejs-logo.svg ./processNodejsImage.sh ./getNodejsImage.js child process exited with code 0

Encontramos una lista de todos los archivos en nuestro directorio actual y el código de salida del comando, que es 0 ya que se ejecutó correctamente. Aunque nuestro directorio actual tiene un número pequeño de archivos, si ejecutamos este código en nuestro directorio de inicio, nuestro programa listará cada archivo único en una carpeta accesible para nuestro usuario. Debido a que tiene un resultado potencialmente grande, usar la función spawn() es lo más ideal porque los flujos no requieren tanta memoria como un búfer grande.

Hasta ahora hemos usado funciones para crear procesos secundarios para ejecutar comandos externos en nuestro sistema operativo. Node.js también ofrece una forma de crear un proceso secundario que ejecuta otros programas Node.js. Vamos a usar la función fork() para crear un proceso secundario para un módulo Node.js en la siguiente sección.

Paso 3: Crear un proceso secundario con fork()

Node.js proporciona la función fork(), una variación de spawn(), para crear un proceso secundario que es también un proceso Node.js. El principal beneficio de usar fork() para crear un proceso Node.js en comparación con spawn() o exec() es que fork() permite la comunicación entre el proceso principal y el secundario.

Con fork(), además de recuperar datos desde el proceso secundario, un proceso principal puede enviar mensajes al proceso secundario en ejecución. Del mismo modo, el proceso secundario puede enviar mensajes al proceso principal.

Vamos a ver un ejemplo en el que usar fork() para crear un nuevo proceso secundario de Node.js puede mejorar el rendimiento de nuestra aplicación. Los programas Node.js se ejecutan sobre un proceso único. Por tanto, las tareas intensivas para la CPU, como la repetición sobre grandes bucles o analizar grandes archivos JSON impiden que otros códigos JavaScript se ejecuten. Para ciertas aplicaciones, esta no es una opción viable. Si un servidor web está bloqueado, no puede procesar ninguna nueva solicitud entrante hasta que el código de bloqueo haya completado su ejecución.

Vamos a ver esto en la práctica creando un servidor web con dos endpoints. Un endpoint realizará una computación lenta que bloquea el proceso Node.js. El otro endpoint devolverá un objeto JSON que dice hello.

Primero, cree un nuevo archivo llamado httpServer.js que tendrá el código para nuestro servidor HTTP:

  1. nano httpServer.js

Empezaremos configurando el servidor HTTP. Esto implica importar el módulo http, creando una función oyente de la solicitud, creando un objeto de servidor y escuchando las solicitudes sobre el objeto del servidor. Si desea profundizar en la creación de servidores HTTP en Node.js o desea repasar estos conceptos, puede leer nuestra guía sobre Cómo crear un servidor web en Node.js con el módulo HTTP.

Introduzca el siguiente código en su editor de texto para configurar un servidor HTTP:

~/child-processes/httpServer.js
const http = require('http');

const host = 'localhost';
const port = 8000;

const requestListener = function (req, res) {};

const server = http.createServer(requestListener);
server.listen(port, host, () => {
  console.log(`Server is running on http://${host}:${port}`);
});

Este código configura un servidor HTTP que se ejecutará en http://localhost:8000. Utiliza literales de plantilla para generar dinámicamente esa URL.

A continuación, escribiremos una función intencionadamente lenta que cuenta en un bucle 5 mil millones de veces. Antes de la función requestListener(), añada el siguiente código:

~/child-processes/httpServer.js
...
const port = 8000;

const slowFunction = () => {
  let counter = 0;
  while (counter < 5000000000) {
    counter++;
  }

  return counter;
}

const requestListener = function (req, res) {};
...

Esto utiliza la sintaxis de la función de flecha para crear un bucle while que cuenta hasta 5000000000.

Para completar este módulo, debemos añadir código a la función requestListener(). Nuestra función invocará slowFunction() en una subruta, y devolverá un mensaje JSON pequeño para la otra. Añada el siguiente código al módulo:

~/child-processes/httpServer.js
...
const requestListener = function (req, res) {
  if (req.url === '/total') {
    let slowResult = slowFunction();
    let message = `{"totalCount":${slowResult}}`;

    console.log('Returning /total results');
    res.setHeader('Content-Type', 'application/json');
    res.writeHead(200);
    res.end(message);
  } else if (req.url === '/hello') {
    console.log('Returning /hello results');
    res.setHeader('Content-Type', 'application/json');
    res.writeHead(200);
    res.end(`{"message":"hello"}`);
  }
};
...

Si el usuario llega al servidor en la subruta /total, ejecutaremos slowFunction(). Si llegamos a la subruta /hello, devolveremos este mensaje JSON: {"message":"hello"}.

Guarde y salga del archivo pulsando CTRL+X.

Como prueba, ejecute este módulo del servidor con node:

  1. node httpServer.js

Cuando se inicie nuestro servidor, la consola mostrará lo siguiente:

Output
Server is running on http://localhost:8000

Ahora, para probar el rendimiento de nuestro módulo, abra dos terminales adicionales. En el primer terminal, utilice el comando curl para realizar una solicitud al endpoint /total, que esperamos que sea lento:

  1. curl http://localhost:8000/total

En el otro terminal, utilice curl para realizar una solicitud al endpoint /hello, de esta forma:

  1. curl http://localhost:8000/hello

La primera solicitud devolverá el siguiente JSON:

Output
{"totalCount":5000000000}

Mientras que la segunda solicitud devolverá este JSON:

Output
{"message":"hello"}

La solicitud para /hello se completó solo tras la solicitud para /total. slowFunction() impidió que el resto de códigos se ejecutasen mientras aún estaba en su bucle. Puede verificar esto observando el resultado del servidor Node.js que se registró en su terminal original:

Output
Returning /total results Returning /hello results

Para procesar el código de bloqueo mientras aún acepta solicitudes entrantes, podemos mover el código de bloqueo a un proceso secundario con fork(). Moveremos el código de bloqueo a su propio módulo. El servidor Node.js creará un proceso secundario cuando alguien acceda al endpoint /total y escuche los resultados de este proceso secundario.

Refactorice el servidor creando primero un nuevo módulo llamado getCount.js que contendrá slowFunction():

  1. nano getCount.js

Ahora introduzca el código para slowFunction() de nuevo:

~/child-processes/getCount.js
const slowFunction = () => {
  let counter = 0;
  while (counter < 5000000000) {
    counter++;
  }

  return counter;
}

Ya que este módulo será un proceso secundario creado con fork(), podemos añadir además código para que se comunique con el proceso principal cuando slowFunction() haya completado el procesamiento. Añada el siguiente bloque de código que envía un mensaje al proceso principal con el JSON que se devolverá al usuario:

~/child-processes/getCount.js
const slowFunction = () => {
  let counter = 0;
  while (counter < 5000000000) {
    counter++;
  }

  return counter;
}

process.on('message', (message) => {
  if (message == 'START') {
    console.log('Child process received START message');
    let slowResult = slowFunction();
    let message = `{"totalCount":${slowResult}}`;
    process.send(message);
  }
});

Vamos a descomponer este bloque de código. Los mensajes entre un proceso principal y uno secundario creados por fork() están accesibles a través del objeto process global de Node.js. Añadimos un oyente a la variable process para que busque eventos message. Una vez que recibamos un evento message, comprobaremos si es el evento START. Nuestro código de servidor enviará el evento START cuando alguien acceda al endpoint /total. Tras recibir ese evento, ejecutaremos slowFunction() y crearemos una cadena JSON con el resultado de la función. Usaremos process.send() para enviar un mensaje al proceso principal.

Guarde y salga de getCount.js introduciendo CTRL+X en nano.

Ahora, vamos a modificar el archivo httpServer.js de forma que, en vez de invocar slowFunction(), cree un proceso secundario que ejecute getCount.js.

Vuelva a abrir httpServer.js con nano:

  1. nano httpServer.js

Primero, importe la función fork() desde el módulo child_process:

~/child-processes/httpServer.js
const http = require('http');
const { fork } = require('child_process');
...

A continuación, eliminaremos la función slowFunction() de este módulo y modificaremos la función requestListener() para crear un proceso secundario. Cambie el código en su archivo de forma que tenga este aspecto:

~/child-processes/httpServer.js
...
const port = 8000;

const requestListener = function (req, res) {
  if (req.url === '/total') {
    const child = fork(__dirname + '/getCount');

    child.on('message', (message) => {
      console.log('Returning /total results');
      res.setHeader('Content-Type', 'application/json');
      res.writeHead(200);
      res.end(message);
    });

    child.send('START');
  } else if (req.url === '/hello') {
    console.log('Returning /hello results');
    res.setHeader('Content-Type', 'application/json');
    res.writeHead(200);
    res.end(`{"message":"hello"}`);
  }
};
...

Cuando alguien vaya al endpoint /total, crearemos un nuevo proceso secundario con fork(). El argumento de fork() es la ruta hacia el módulo Node.js. En este caso, es el archivo getCount.js de nuestro directorio actual, que recibimos de __dirname. La referencia a este proceso secundario se almacena en una variable child.

A continuación, añadiremos un oyente al objeto child. Este oyente captura cualquier mensaje que el proceso secundario nos proporcione. En este caso, getCount.js devolverá una cadena JSON con la cantidad total contada por el bucle while. Cuando recibamos ese mensaje, enviaremos el JSON al usuario.

Usaremos la función send() de la variable child para proporcionarle un mensaje. Este programa envía el mensaje START, que inicia la ejecución de slowFunction() en el proceso secundario.

Guarde y salga de nano introduciendo CTRL+X.

Para probar la mejora que el uso de fork() ha realizado sobre el servidor HTTP, comience ejecutando el archivo httpServer.js con node:

  1. node httpServer.js

Al igual que antes, dará como resultado el siguiente mensaje cuando se inicie:

Output
Server is running on http://localhost:8000

Para probar el servidor, necesitaremos dos terminales adicionales como hicimos la primera vez. Puede reutilizarlas si aún están abiertas.

En el primer terminal, utilice el comando curl para realizar una solicitud al endpoint /total, que tardará un poco en computarse:

  1. curl http://localhost:8000/total

En el otro terminal, utilice curl para realizar una solicitud al endpoint /hello, que responde en un breve periodo de tiempo:

  1. curl http://localhost:8000/hello

La primera solicitud devolverá el siguiente JSON:

Output
{"totalCount":5000000000}

Mientras que la segunda solicitud devolverá este JSON:

Output
{"message":"hello"}

A diferencia de la primera vez que probamos esto, la segunda solicitud a /hello se ejecuta de inmediato. Puede confirmar revisando los registros, que tendrán este aspecto:

Output
Child process received START message Returning /hello results Returning /total results

Estos registros muestran que la solicitud para el endpoint /hello se ejecutaron después de haberse creado el proceso secundario pero antes de que el proceso secundario hubiese terminado su tarea.

Ya que movimos el código de bloqueo de un proceso secundario usando fork(), el servidor pudo responder a otras solicitudes y ejecutar otros códigos JavaScript. Debido a la capacidad de transmisión de mensaje de la función fork(), podemos controlar cuándo un proceso secundario inicia una actividad y podemos devolver datos desde un proceso secundario a un proceso principal.

Conclusión

En este artículo, ha utilizado varias funciones para crear un proceso secundario en Node.js. Primero, creó procesos secundarios con exec() para ejecutar comandos shell desde el código Node.js. Luego, ejecutó un archivo ejecutable con la función execFile(). Ha visto la función spawn(), que también puede ejecutar comandos, pero devuelve datos a través de un flujo y no inicia un shell como exec() y execFile(). Finalmente, utilizó la función fork() para permitir comunicación bidireccional entre el proceso principal y secundario.

Para obtener más información sobre el módulo child_process, puede leer la documentación de Node.js. Si desea continuar aprendiendo sobre Node.js, puede regresar a la serie Cómo programar en Node.js o consultar proyectos de programación y configuraciones en nuestra página temática de Node.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors

Default avatar

Senior Technical Editor

Editor at DigitalOcean, fiction writer and podcaster elsewhere, always searching for the next good nautical pun!


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.