Tutorial

Comment lancer des processus enfants dans Node.js

Node.jsDevelopmentJavaScript

L'auteur a choisi le COVID-19 Relief Fund pour recevoir un don dans le cadre du programme Write for DOnations.

Introduction

Lorsqu'un utilisateur lance un programme Node.js, ce dernier s'exécute comme un seul système d'exploitation (OS) *qui représente l'instance du programme en cours d'exécution. Dans le cadre de ce processus, Node.js exécute des programmes sur un seul thread. Comme mentionné plus haut dans cette série avec le tutoriel Comment écrire du code asynchrone dans Node.js, car un seul thread peut fonctionner sur un seul processus, les opérations qui prennent du temps à s'exécuter dans JavaScript peuvent bloquer le thread Node.js et retarder l'exécution d'un autre code. Une stratégie clé pour contourner ce problème consiste à lancer un *processus enfant, ou un processus créé par un autre processus, lorsqu'il est confronté à des tâches de longue haleine. Lorsqu'un nouveau processus est lancé, le système d'exploitation peut utiliser des techniques de multitraitement pour s'assurer que le processus principal de Node.js et le processus enfant supplémentaire s'exécutent simultanément ou en même temps.

Node.js comprend le module child_process qui dispose de fonctions pour créer de nouveaux processus. En plus de traiter les tâches de longue haleine, ce module peut également s'interfacer avec l'OS et exécuter des commandes shell. Les administrateurs système peuvent utiliser Node.js pour exécuter les commandes shell pour structurer et maintenir leurs opérations en tant que module Node.js au lieu de scripts shell.

Dans ce tutoriel, vous allez créer des processus enfants tout en exécutant une série d'exemples d'applications Node.js. Vous allez créer des processus avec le module child_process en récupérant les résultats d'un processus enfant via un buffer ou une chaîne avec la fonction exec(), puis à partir d'un flux de données avec la fonction spawn(). Vous terminerez en utilisant fork() pour créer un processus enfant d'un autre programme Node.js avec lequel vous pouvez communiquer au moment où il s'exécute. Pour illustrer ces concepts, vous allez écrire un programme pour lister le contenu d'un répertoire, un programme pour trouver des fichiers et un serveur web avec plusieurs terminaux.

Conditions préalables

Étape 1 - Création d'un processus enfant avec exec()

Les développeurs créent généralement des processus enfants pour exécuter des commandes sur leur système d'exploitation lorsqu'ils ont besoin de manipuler la sortie de leurs programmes Node.js avec un shell, comme en utilisant le piping shell ou la redirection. La fonction exec() dans Node.js crée un nouveau processus shell et exécute une commande dans ce shell. La sortie de la commande est maintenue dans un buffer en mémoire, que vous pouvez accepter via une fonction de rappel passée dans exec().

Commençons à créer nos premiers processus enfants dans Node.js. Tout d'abord, nous devons créer notre environnement de codage pour stocker les scripts que nous allons créer tout au long de ce tutoriel. Dans le terminal, créez un dossier appelé child-processes :

  • mkdir child-processes

Entrez ce dossier dans le terminal avec la commande cd :

  • cd child-processes

Créez un nouveau fichier appelé listFiles.js et ouvrez le fichier dans un éditeur de texte. Dans ce tutoriel, nous utiliserons nano, un éditeur de texte de terminal :

  • nano listFiles.js

Nous allons écrire un module Node.js qui utilise la fonction exec() pour exécuter la commande ls. La commande ls liste les fichiers et les dossiers dans un répertoire. Ce programme prend la sortie de la commande ls et l'affiche à l'utilisateur.

Dans l'éditeur de texte, ajoutez le code suivant :

~/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}`);
});

Nous importons d'abord la commande exec() du module child_process, en utilisant la déstructuration JavaScript. Une fois importé, nous utilisons la fonction exec(). Le premier argument est la commande que nous aimerions exécuter. Dans ce cas, il s'agit de ls -lh, qui liste tous les fichiers et les dossiers dans le répertoire actuel en format long, avec une taille totale de fichier en unités lisibles par l'homme en tête de la sortie.

Le deuxième argument est une fonction de rappel avec trois paramètres : error, stdout, et stderr. Si la commande ne s'exécute pas, error indiquera la raison de l'échec. Cela peut se produire si le shell ne peut pas trouver la commande que vous essayez d'exécuter. Si la commande est exécutée avec succès, toute donnée qu'elle écrit dans le flux de sortie standard est capturée dans stdout, et toute donnée qu'elle écrit dans le flux d'erreurs standard est capturée dans stderr.

Remarque : il est important de garder en tête la différence entre error et stderr. Si la commande elle-même ne s'exécute pas, error capturera l'erreur. Si la commande s'exécute mais renvoie la sortie dans le flux d'erreur, stderr la capturera. Les programmes Node.js les plus résilients traiteront toutes les sorties possibles d'un processus enfant.

Dans notre fonction de rappel, nous vérifions d'abord si nous avons reçu une erreur. Si c'est le cas, nous affichons le message de l'erreur (une propriété de l'objet Error) avec console.error() et terminons la fonction avec return. Nous vérifions alors si la commande a imprimé un message d'erreur et return si c'est le cas. Si la commande s'exécute avec succès, nous enregistrons la sortie de la console avec console.log().

Exécutons ce fichier pour le voir en action. Tout d'abord, enregistrez et quittez nano en appuyant sur CTRL+X.

De retour dans votre terminal, lancez votre application avec la commande node :

  • node listFiles.js

Votre terminal affichera la sortie suivante :

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

Ceci liste le contenu du répertoire child-processes en format long, ainsi que la taille du contenu en haut. Vos résultats auront votre propre utilisateur et groupe à la place de sammy. Cela montre que le programme listFiles.js a exécuté avec succès la commande shell ls -lh.

Voyons maintenant un autre moyen d'exécuter des processus concurrents. Le module child_process de Node.js peut également exécuter des fichiers exécutables avec la fonction execFile(). La différence essentielle entre les fonctions execFile() et exec() est que le premier argument execFile() est maintenant un chemin vers un fichier exécutable au lieu d'une commande. La sortie du fichier exécutable est stockée dans un buffer comme exec(), que nous accédons via une fonction de rappel avec les paramètres error, stdout et stderr.

Remarque : les scripts dans Windows comme .bat et .cmd ne peuvent pas être exécutés avec execFile() car la fonction ne crée pas de shell lors de l'exécution du fichier. Sous Unix, Linux et macOS, les scripts exécutables n'ont pas toujours besoin d'un shell s'exécuter. Cependant, une machine Windows a besoin d'un shell pour exécuter des scripts. Pour exécuter des fichiers script sous Windows, utilisez exec(), puisqu'il crée un nouveau shell. Vous pouvez également utiliser spawn(), que vous utiliserez plus loin dans cette étape.

Cependant, notez que vous pouvez exécuter avec succès des fichiers .exe dans Windows en utilisant execFile(). Cette limitation ne s'applique qu'aux fichiers script qui nécessitent un shell pour s'exécuter.

Commençons par ajouter un script exécutable pour exécuter execFile(). Nous allons écrire un script bash qui téléchargera le logo Node.js du site Node.js et Base64 l'encode pour convertir ses données en une chaîne de caractères ASCII.

Créez un nouveau fichier script shell appelé processNodejsImage.sh :

  • nano processNodejsImage.sh

Écrivez maintenant un script pour télécharger l'image et base64 la convertit :

~/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 première déclaration est une déclaration shebang. Elle est utilisée dans Unix, Linux et macOS lorsque nous voulons spécifier un shell pour exécuter notre script. La deuxième déclaration est une commande curl. L’utilitaire cURL, dont la commande est curl, est un outil en ligne de commande qui peut transférer des données vers et en provenance d'un serveur. Nous utilisons cURL pour télécharger le logo Node.js du site web, et nous utilisons redirection pour enregistrer les données téléchargées dans un nouveau fichier nodejs-logo.svg. La dernière déclaration utilise l'utilitaire base64 pour encoder le fichier nodejs-logo.svg que nous avons téléchargé avec cURL. Le script envoie alors la chaîne encodée à la console.

Enregistrez et quittez avant de continuer.

Pour que notre programme Node exécute le script bash, nous devons le rendre exécutable. Pour ce faire, lancez ce qui suit :

  • chmod u+x processNodejsImage.sh

Cela donnera à votre utilisateur actuel la permission d'exécuter le fichier.

Une fois notre script en place, nous pouvons écrire un nouveau module Node.js pour l'exécuter. Ce script utilisera execFile() pour exécuter le script dans un processus enfant, en détectant toute erreur et en affichant toute sortie sur la console.

Sur votre terminal, créez un nouveau fichier JavaScript appelé getNodejsImage.js :

  • nano getNodejsImage.js

Tapez le code suivant dans l'éditeur de texte :

~/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}`);
});

Nous utilisons la déstructuration JavaScript pour importer la fonction execFile() du module child_process. Nous utilisons alors cette fonction, en passant le chemin du fichier comme prénom. __dirname contient le chemin du répertoire du module dans lequel il est écrit. Node.js fournit la variable __dirname à un module lorsque le module s'exécute. En utilisant __dirname, notre script trouvera toujours le fichier processNodejsImage.sh sur différents systèmes d'exploitation, quel que soit l'endroit où nous exécutons getNodejsImage.js. Notez que pour la configuration actuelle de notre projet, getNodejsImage.js et processNodejsImage.sh doivent se trouver dans le même dossier.

Le deuxième argument est un rappel avec les paramètres error, stdout, et stderr. Comme avec notre exemple précédent qui a utilisé exec(), nous vérifions chaque sortie possible du fichier script et les enregistrons dans la console.

Dans votre éditeur de texte, enregistrez ce fichier et quittez l'éditeur.

Dans votre terminal, utilisez node pour exécuter le module:

  • node getNodejsImage.js

L'exécution de ce script produira une sortie comme celle-ci :

Output
stdout: PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge ...

Notez que nous avons tronqué la sortie dans cet article en raison de sa grande taille.

Avant d'encoder l'image en base64, processNodejsImage.sh la télécharge. Vous pouvez également vérifier que vous avez téléchargé l'image en inspectant le répertoire actuel.

Exécutez listFiles.js pour trouver la liste mise à jour des fichiers dans notre répertoire :

  • node listFiles.js

Le script affichera un contenu similaire à celui qui suit sur le 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

Nous avons maintenant exécuté avec succès processNodejsImage.sh en tant que processus enfant dans Node.js en utilisant la fonction execFile().

Les fonctions exec() et execFile() peuvent exécuter des commandes sur le shell du système d'exploitation dans un processus enfant Node.js. Node.js fournit également une autre méthode avec une fonctionnalité similaire, spawn(). La différence est qu'au lieu d'obtenir la sortie des commandes shell en même temps, nous les recevons en morceaux via un flux. Dans la section suivante, nous utiliserons la commande spawn() pour créer un processus enfant.

Étape 2 — Création d'un processus enfant avec spawn()

La fonction spawn() exécute une commande dans un processus. Cette fonction renvoie des données via l’API stream. Par conséquent, pour obtenir la sortie du processus enfant, nous devons écouter les événements du flux.

Les flux dans Node.js sont des instances d'émetteurs d'événements. Si vous souhaitez en savoir plus sur l'écoute des événements et les fondements de l'interaction avec les flux, vous pouvez lire notre guide sur Utiliser des émetteurs d'événements dans Node.js.

Il est souvent judicieux de choisir spawn() plutôt exec() ou execFile() lorsque la commande que vous voulez exécuter peut produire une grande quantité de données. Avec un buffer, tel qu'utilisé par exec() et execFile(), toutes les données traitées sont stockées dans la mémoire de l'ordinateur. Pour de grandes quantités de données, cela peut dégrader la performance du système. Avec un flux, les données sont traitées et transférées en petits groupes. Par conséquent, vous pouvez traiter une grande quantité de données sans utiliser trop de mémoire à la fois.

Voyons comment nous pouvons utiliser spawn() pour créer un processus enfant. Nous allons écrire un nouveau module Node.js qui crée un processus enfant pour exécuter la commande find. Nous utiliserons la commande find pour lister tous les fichiers du répertoire actuel.

Créez un nouveau fichier appelé findFiles.js :

  • nano findFiles.js

Dans votre éditeur de texte, commencez par appeler la commande spawn() :

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

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

Nous avons d'abord importé la fonction spawn() du module child_process. Nous avons ensuite appelé la fonction spawn() pour créer un processus enfant qui exécute la commande find. Nous tenons la référence au processus dans la variable child, que nous utiliserons pour écouter ses événements en flux.

Le premier argument en spawn() est la commande à exécuter, dans ce cas find. Le deuxième argument est un tableau qui contient les arguments pour la commande exécutée. Dans ce cas, nous disons à Node.js d'exécuter la commande find avec l'argument ., ce qui fait que la commande trouve tous les fichiers du répertoire courant. La commande équivalente dans le terminal est find ..

Avec les fonctions exec() et execFile(), nous avons écrit les arguments en même temps que la commande dans une seule chaîne. Cependant, avec spawn(), tous les arguments des commandes doivent être entrés dans le tableau. Ceci parce que spawn(), contrairement à exec() et execFile(), ne crée pas de nouveau shell avant d'exécuter un processus. Pour avoir des commandes avec leurs arguments dans une seule chaîne, vous devez également créer un nouveau shell.

Continuons notre module en ajoutant des auditeurs pour la sortie de la commande. Ajoutez les lignes surlignées suivantes :

~/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}`);
});

Les commandes peuvent renvoyer des données dans le flux stdout ou dans le flux stderr, vous avez donc ajouté des auditeurs pour les deux. Vous pouvez ajouter des écouteurs en appelant la méthode on() des objets de chaque flux. L'événement data des flux nous donne la sortie de la commande vers ce flux. Chaque fois que nous obtenons des données sur l'un ou l'autre des flux, nous les enregistrons dans la console.

Nous écoutons ensuite deux autres événements : l'événement error si la commande ne s'exécute pas ou est interrompue, et l'événement close lorsque la commande a fini d'exécuter, fermant ainsi le flux.

Dans l'éditeur de texte, complétez le module Node.js en écrivant les lignes en surbrillance suivantes :

~/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}`);
});

Pour les événements error et close, vous configurez un auditeur directement sur la variable child. Lors de l'écoute des événements error, si une erreur survient, Node.js fournit un objet Error. Dans ce cas, vous enregistrez la propriété message de l'erreur.

Lorsqu'il écoute l'événement close, Node.js fournit le code exit de la commande. Un code exit indique si la commande s'est exécuté avec succès ou non. Lorsqu'une commande s'exécute sans erreurs, elle renvoie la valeur la plus basse possible pour un code exit : 0. Lorsqu'elle s'exécute avec une erreur, elle renvoie un code différent de zéro.

Le module est terminé. Enregistrez et quittez nano avec CTRL+X.

Maintenant, lancez le code avec la commande node :

  • node findFiles.js

Une fois terminé, vous verrez la sortie suivante :

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

Nous trouvons une liste de tous les fichiers dans notre répertoire actuel et le code exit de la commande, qui est 0 car il s'est exécuté avec succès. Bien que notre répertoire actuel contienne un petit nombre de fichiers, si nous avions exécuté ce code dans notre répertoire d'origine, notre programme aurait énuméré chaque fichier dans chaque dossier accessible à notre utilisateur. Étant donné que la sortie peut être potentiellement très importante, l'utilisation de la fonction spawn() est la plus idéale car ses flux ne nécessitent pas autant de mémoire qu'un grand buffer.

Jusqu'à présent, nous avons utilisé des fonctions pour créer des processus enfants afin d'exécuter des commandes externes dans notre système d'exploitation. Node.js fournit également un moyen de créer un processus enfant qui exécute d'autres programmes Node.js. Utilisons la fonction fork() pour créer un processus enfant pour un module Node.js dans la section suivante.

Étape 3 — Création d'un processus enfant avec fork()

Node.js fournit la fonction fork(), une variation du spawn(), pour créer un processus enfant qui est également un processus Node.js. Le principal avantage d'utiliser fork() pour créer un processus Node.js par rapport à spawn() ou exec() est que fork() permet la communication entre le processus parent et le processus enfant.

Avec fork(), en plus de récupérer des données du processus enfant, un processus parent peut envoyer des messages au processus enfant en cours d'exécution. De la même façon, le processus enfant peut envoyer des messages au processus parent.

Voyons un exemple où l'utilisation de fork() pour créer un nouveau processus enfant Node.js peut améliorer les performances de notre application. Les programmes Node.js s'exécutent sur un seul processus. Par conséquent, les tâches gourmandes en CPU comme l'itération sur des grandes boucles ou l'analyse de gros fichiers JSON empêchent l'exécution d'autres codes JavaScript. Pour certaines applications, ce n'est pas une option viable. Si un serveur web est bloqué, il ne peut pas traiter de nouvelles demandes entrantes tant que le code qui le bloque n'a pas fini son exécution.

Voyons cela en pratique en créant un serveur web avec deux points terminaux. L'un d'eux effectuera un calcul lent qui bloquera le processus Node.js. L'autre point terminal renverra un objet JSON disant hello.

Tout d'abord, créez un nouveau fichier appelé httpServer.js, qui contiendra le code de notre serveur HTTP :

  • nano httpServer.js

Nous commencerons par configurer le serveur HTTP. Cela implique l'importation du module http, la création d'une fonction d'écoute des requêtes, la création d'un objet serveur et l'écoute des requêtes sur l'objet serveur. Si vous souhaitez vous plonger plus profondément dans la création de serveurs HTTP dans Node.js ou si vous souhaitez vous rafraîchir la mémoire, vous pouvez lire notre guide Comment créer un serveur web en Node.js avec le module HTTP.

Entrez le code suivant dans votre éditeur de texte pour configurer un serveur 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}`);
});

Ce code met en place un serveur HTTP qui s'exécutera sur http://localhost:8000. Elle utilise des littéraux de gabarits pour générer dynamiquement cette URL.

Ensuite, nous allons écrire une fonction intentionnellement lente qui compte 5 milliards de fois dans une boucle. Avant la fonction requestListener(), ajoutez le code suivant :

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

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

  return counter;
}

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

Cela utilise la syntaxe de fonction fléchée pour créer une boucle while qui compte jusqu'à 5000000000.

Pour terminer ce module, nous devons ajouter du code à la fonction requestListener(). Notre fonction appellera la fonction slowFunction() sur le sous-chemin et renverra un petit message JSON pour l'autre. Ajoutez le code suivant au module :

~/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 l'utilisateur atteint le serveur dans le sous-chemin /total, nous exécutons alors slowFunction(). Si nous atteignons le sous-chemin /hello, nous renvoyons ce message JSON : {"message":"hello"}.

Enregistrez et quittez le fichier en appuyant sur CTRL+X.

Pour tester, lancez ce module serveur avec node :

  • node httpServer.js

Lorsque notre serveur démarre, la console affiche ce qui suit :

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

Maintenant, pour tester les performances de notre module, ouvrez deux terminaux supplémentaires. Sur le premier terminal, utilisez la commande curl pour faire une requête vers le point terminal /total, qui devrait être lent:

  • curl http://localhost:8000/total

Dans l'autre terminal, utilisez curl pour faire une requête au point terminal /hello :

  • curl http://localhost:8000/hello

La première requête renverra le JSON suivant :

Output
{"totalCount":5000000000}

Alors que la deuxième requête renverra ce JSON :

Output
{"message":"hello"}

La requête à /hello est complétée qu'après la requête à /total. Le slowFunction() a bloqué l'exécution de tout autre code alors qu'il se trouvait toujours dans sa boucle. Vous pouvez vérifier cela en regardant la sortie du serveur Node.js qui a été enregistrée dans votre terminal original :

Output
Returning /total results Returning /hello results

Pour traiter le code de blocage tout en acceptant les requêtes entrantes, nous pouvons déplacer le code de blocage vers un processus enfant avec fork(). Nous allons déplacer le code de blocage dans son propre module. Le serveur Node.js créera alors un processus enfant lorsque quelqu'un accède au point terminal /total et écoutera les résultats de ce processus enfant.

Remaniez le serveur en créant d'abord un nouveau module appelé getCount.js qui contiendra slowFunction() :

  • nano getCount.js

Entrez encore une fois le code pour slowFunction() :

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

  return counter;
}

Comme ce module sera un processus enfant créé avec fork(), nous pouvons également ajouter du code pour communiquer avec le processus parent lorsque slowFunction() a terminé le traitement. Ajoutez le bloc de code suivant qui envoie un message au processus parent avec le JSON à retourner à l'utilisateur :

~/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);
  }
});

Décomposons ce bloc de code. Les messages entre un processus parent et un processus enfant créés par fork() sont accessibles via l’objet process global Node.js. Nous ajoutons un auditeur à la variable process pour rechercher des événements message. Une fois que nous recevons un événement message, nous vérifions si c'est l'événement START. Notre code serveur enverra l'événement START lorsque quelqu'un accède au point terminal /total. À la réception de cet événement, nous exécutons slowFunction() et créons une chaîne JSON avec le résultat de la fonction. Nous utilisons process.send() pour envoyer un message au processus parent.

Enregistrez et quittez getCount.js en entrant CTRL+X dans nano.

Maintenant, modifions le fichier httpServer.js afin qu'au lieu d'appeler slowFunction(), il crée un processus enfant qui exécute getCount.js.

Rouvrez httpServer.js avec nano :

  • nano httpServer.js

Tout d'abord, importez la fonction fork() du module child_process :

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

Ensuite, nous allons supprimer la fonction slowFunction() de ce module et modifier la fonction requestListener() pour créer un processus enfant. Modifiez le code dans votre fichier afin qu'il ressemble à ceci :

~/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"}`);
  }
};
...

Lorsque quelqu'un passe au point terminal /total, nous créons maintenant un nouveau processus enfant avec fork(). L'argument de fork() est le chemin vers le module Node.js. Dans ce cas, il s'agit du fichier getCount.js dans notre répertoire actuel, que nous recevons de __dirname. La référence à ce processus enfant est stockée dans une variable child.

Nous ajoutons alors un auditeur à l'objet child. Cet auditeur capture tous les messages que le processus enfant nous donne. Dans ce cas, getCount.js renverra une chaîne JSON avec le nombre total compté par la boucle while. Lorsque nous recevons ce message, nous envoyons le JSON à l'utilisateur.

Nous utilisons la fonction send() de la variable child pour lui donner un message. Ce programme envoie le message START, qui commence l'exécution de slowFunction() dans le processus enfant.

Enregistrez et quittez nano en entrant CTRL+X.

Pour tester l'amélioration en utilisant fork() fait sur le serveur HTTP, commencez par exécuter le fichier httpServer.js avec node :

  • node httpServer.js

Comme auparavant, il sortira le message suivant lorsqu'il se lancera :

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

Pour tester le serveur, nous aurons besoin de deux terminaux supplémentaires, comme nous l'avons fait la première fois. Vous pouvez les réutiliser s'ils sont toujours ouverts.

Dans le premier terminal, utilisez la commande curl pour faire une requête vers le point terminal /total, qui prend un certain temps à calculer :

  • curl http://localhost:8000/total

Dans l'autre terminal, utilisez curl pour faire une requête au point terminal /hello, qui répond en peu de temps :

  • curl http://localhost:8000/hello

La première requête renverra le JSON suivant :

Output
{"totalCount":5000000000}

Alors que la deuxième requête renverra ce JSON :

Output
{"message":"hello"}

Contrairement à la première fois que nous avons essayé ceci, la deuxième requête vers /hello s'exécute immédiatement. Vous pouvez confirmer en examinant les journaux, qui ressembleront à ceci :

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

Ces journaux montrent que la requête concernant le point terminal /hello s'est exécutée après la création du processus enfant, mais avant que celui-ci n'ait terminé sa tâche.

Comme nous avons déplacé le code de blocage dans un processus enfant en utilisant fork(), le serveur a toujours pu répondre à d'autres requêtes et exécuter d'autres codes JavaScript. Grâce à la capacité de la fonction fork() à transmettre des messages, nous pouvons contrôler le moment où un processus enfant commence une activité et nous pouvons renvoyer des données d'un processus enfant à un processus parent.

Conclusion

Dans cet article, vous avez utilisé diverses fonctions pour créer un processus enfant dans Node.js. Vous avez d'abord créé des processus enfants avec exec() pour exécuter des commandes shell à partir du code Node.js. Vous avez ensuite exécuté un fichier exécutable avec la fonction execFile(). Vous avez examiné la fonction spawn(), qui peut également exécuter des commandes mais renvoie des données via un flux et ne démarre pas un shell comme exec() et execFile(). Enfin, vous avez utilisé la fonction fork() pour permettre une communication bidirectionnelle entre les processus parent et enfant.

Pour en savoir plus sur le module child_process, vous pouvez lire la documentation Node.js. Si vous souhaitez continuer à apprendre Node.js, vous pouvez revenir à la série Comment coder dans Node.js, ou parcourir les projets de programmation et les configurations sur notre page thématique Node.

Creative Commons License