Tutorial

Como lançar processos filhos no Node.js

Node.jsDevelopmentJavaScript

O autor selecionou a COVID-19 Relief Fund para receber uma doação como parte do programa Write for DOnations.

Introdução

Quando um usuário executa um único programa no Node.js, ele é executado como um único processo de sistema operacional (OS) que representa a instância do programa em execução. Dentro desse processo, o Node.js executa programas em uma única thread. Como mencionado anteriormente nesta série com o tutorial How To Write Asynchronous Code in Node.js, como apenas uma thread pode ser executada em um processo, operações que levam muito tempo para ser executadas em JavaScript podem bloquear a thread do Node.js e atrasar a execução de outro código. Uma estratégia importante para contornar este problema é lançar um processo filho, ou um processo criado por outro processo, quando confrontado com tarefas de longa execução. Quando um novo processo é lançado, o sistema operacional pode empregar técnicas de multiprocessamento para garantir que o processo principal do Node.js e o processo filho adicional executem concorrentemente ou ao mesmo tempo.

O Node.js inclui o módulo child_process, que possui funções para criar novos processos. Além de lidar com tarefas de longa execução, este módulo também pode interagir com o SO e executar comandos do shell. Administradores de sistema podem usar o Node.js para executar comandos do shell para estruturar e manter suas operações como um módulo Node.js em vez de scripts de shell.

Neste tutorial, você criará processos filhos ao executar uma série de aplicações de exemplo do Node.js. Você criará processos com o módulo child_process recuperando os resultados de um processo filho através de um buffer ou uma string com a função exec(), e depois a partir de uma stream de dados com a função spawn(). Você terminará usando o fork() para criar um processo filho de outro programa Node.js com o qual você pode se comunicar durante a execução. Para ilustrar esses conceitos, você escreverá um programa para listar o conteúdo de um diretório, um programa para encontrar arquivos, e um servidor Web com vários endpoints.

Pré-requisitos

Passo 1 — Criando um processo filho com exec()

Os desenvolvedores geralmente criam processos filhos para executar comandos no sistema operacional quando precisam manipular a saída de dos programas Node.js com o shell, como ao usar pipes ou redirecionamento. A função exec() no Node.js cria um novo processo de shell e executa um comando nele. A saída do comando é mantida em um buffer na memória, que você pode aceitar através de uma função de callback passada dentro de exec().

Vamos começar criando os primeiros processos filhos no Node.js. Primeiro, precisamos configurar o ambiente de codificação para armazenar os scripts que vamos criar ao longo deste tutorial. No terminal, crie uma pasta chamada child-processes:

  • mkdir child-processes

Entre nessa pasta no terminal com o comando cd:

  • cd child-processes

Crie um novo arquivo chamado listFiles.js e abra o arquivo em um editor de texto. Neste tutorial, usaremos o nano, um editor de texto para terminal:

  • nano listFiles.js

Vamos escrever um módulo Node.js que usa a função exec() para executar o comando ls. O comando ls lista os arquivos e pastas em um diretório. Este programa pega a saída do comando ls e a exibe ao usuário.

No editor de texto, adicione o seguinte 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}`);
});

Primeiro importamos o comando exec() do módulo child_process usando JavaScript destructuring. Depois de importado, usamos a função exec(). O primeiro argumento é o comando que gostaríamos de executar. Neste caso, ele é o ls -lh, que lista todos os arquivos e pastas no diretório atual em formato longo, com um tamanho total de arquivo em unidades legíveis por humanos no topo da saída.

O segundo argumento é uma função de callback com três parâmetros: error, stdout e stderr. Se o comando falhou ao executar, error irá capturar a razão pela qual ele falhou. Isso pode acontecer se o shell não puder encontrar o comando que você está tentando executar. Se o comando for executado com sucesso, todos os dados que ele escreve na stream de saída padrão são capturados no stdout, e todos os dados que ele escreve na stream de erro padrão são capturados no stderr.

Nota: é importante manter a diferença entre error e stderr em mente. Se o comando em si falhar ao executar, error irá capturar o erro. Se o comando executar, mas retornar a saída para a stream de erro, o stderr irá capturá-lo. Os programas Node.js mais resilientes irão lidar com todas as saídas possíveis para um processo filho.

Na função de callback, primeiro verificamos se recebemos um erro. Se sim, exibimos a message do erro (uma propriedade do objeto Error) com console.error() e terminamos a função com return. Em seguida, verificamos se o comando imprimiu uma mensagem de erro e usamos return se positivo. Se o comando for executado com sucesso, registramos a saída no console com console.log().

Vamos executar este arquivo para vê-lo em ação. Primeiro, salve e saia do nano pressionando CTRL+X.

Volte ao seu terminal, execute sua aplicação com o comando node:

  • node listFiles.js

Seu terminal irá exibir a seguinte saída:

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

Isso lista o conteúdo do diretório child-processes em formato longo, juntamente com o tamanho do conteúdo no topo. Seus resultados terão seu próprio usuário e grupo no lugar de sammy. Isso mostra que o programa listFiles.js executou com sucesso o comando de shell ls -lh.

Agora, vamos olhar para uma outra maneira de executar processos simultâneos. O módulo child_process do Node.js também pode rodar arquivos executáveis com a função execFile(). A diferença principal entre as funções execFile() e exec() é que o primeiro argumento de execFile() é agora um caminho para um arquivo executável em vez de um comando. A saída do arquivo executável é armazenada em um buffer como exec(), que acessamos através de uma função de callback com parâmetros error, stdout e stderr.

Nota: os scripts no Windows, como os arquivos .bat e .cmd, não podem ser executados com execFile(), porque a função não cria um shell ao executar o arquivo. No Unix, Linux e macOS, scripts executáveis nem sempre precisam de um shell para serem executados. No entanto, uma máquina Windows precisa de um shell para executar scripts. Para executar arquivos de script no Windows use exec(), uma vez que ele cria um novo shell. Alternativamente, você pode usar spawn(), que você usará mais tarde neste Passo.

No entanto, observe que você pode executar arquivos .exe no Windows usando execFile(). Essa limitação se aplica apenas a arquivos de script que exigem um shell para serem executados.

Vamos começar adicionando um script executável para o execFile() executar. Escreveremos um script bash que baixa o logotipo do Node.js a partir do site do Node.js e o Base64 o codifica para converter seus dados para uma string de caracteres ASCII.

Crie um novo script de shell chamado processNodejsImage.sh:

  • nano processNodejsImage.sh

Agora, escreva um script para baixar a imagem e o base64 convertê-la:

~/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

A primeira declaração é uma declaração de shebang. Ela é usada no Unix, Linux e macOS quando queremos especificar um shell para executar nosso script. A segunda declaração é um comando curl. O utilitário cURL, cujo comando é curl, é uma ferramenta de linha de comando que pode transferir dados de e para um servidor. Usamos o cURL para baixar o logotipo do Node.js a partir do site e, em seguida, usamos o redirecionamento para salvar os dados baixados para um novo arquivo nodejs-logo.svg. A última declaração usa o utilitário base64 para codificar o arquivo nodejs-logo.svg que baixamos com o cURL. O script então entrega a string codificada para o console.

Salve e saia antes de continuar.

Para que nosso programa Node execute o script bash, temos que torná-lo executável. Para fazer isso, execute o seguinte:

  • chmod u+x processNodejsImage.sh

Isso dará ao seu usuário atual a permissão para executar o arquivo.

Com nosso script no lugar, podemos escrever um novo módulo Node.js para executá-lo. Este script usará o execFile() para executar o script em um processo filho, capturando quaisquer erros e exibindo toda a saída no console.

Em seu terminal, crie um novo arquivo JavaScript chamado getNodejsImage.js:

  • nano getNodejsImage.js

Digite o código a seguir no 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}`);
});

Usamos o destructuring do JavaScript para importar a função execFile() a partir do módulo child_process. Em seguida, usamos essa função, passando o caminho do arquivo como primeiro nome. __dirname contém o caminho do diretório do módulo em que ele está escrito. O Node.js fornece a variável __dirname a um módulo quando o módulo é executado. Ao usar __dirname, nosso script irá sempre encontrar o arquivo processNodejsImage.sh entre sistemas operacionais diferentes, independentemente de onde executamos o getNodejsImage.js. Observe que para a configuração atual de projeto, getNodejsImage.js e processNodejsImage.sh devem estar na mesma pasta.

O segundo argumento é um callback com os parâmetros error, stdout e stderr. Assim como no exemplo anterior que usou o exec(), verificamos cada resultado possível do arquivo de script e fazemos o log deles no console.

No editor de texto, salve este arquivo e saia do editor.

No terminal, use o node para executar o módulo:

  • node getNodejsImage.js

A execução deste script produzirá uma saída como esta:

Output
stdout: PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge ...

Observe que truncamos a saída neste artigo devido ao grande tamanho.

Antes do base64 codificar a imagem, o processNodejsImage.sh primeiramente faz o download dela. Verifique também se você baixou a imagem inspecionando o diretório atual.

Execute listFiles.js para encontrar a lista atualizada de arquivos em nosso diretório:

  • node listFiles.js

O script exibirá um conteúdo similar ao seguinte no 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

Agora, executamos com sucesso o processNodejsImage.sh como um processo filho no Node.js usando a função execFile().

As funções exec() e execFile() podem executar comandos no shell do sistema operacional em um processo filho do Node.js. O Node.js também fornece outro método com funcionalidade semelhante, o spawn(). A diferença é que, em vez de obter a saída dos comandos do shell de uma só vez, nós a obtemos em partes através de uma stream. Na próxima seção, usaremos o comando spawn() para criar um processo filho.

Passo 2 — Criando um processo filho com spawn()

A função spawn() executa um comando em um processo. Essa função retorna dados via stream API. Portanto, para obter a saída do processo filho, precisamos ouvir eventos de streams.

As streams no Node.js são instâncias de emissores de eventos. Se você quiser aprender mais sobre como ouvir eventos e conceitos básicos para interagir com streams, leia nosso guia Using Event Emitters in Node.js.

Frequentemente, é uma boa ideia escolher o spawn() em vez de exec() ou execFile() quando o comando que você deseja executar pode gerar uma grande quantidade de dados. Com um buffer, como usado por exec() e execFile(), todos os dados processados são armazenados na memória do computador. Para grandes quantidades de dados, isso pode degradar o desempenho do sistema. Com uma stream, os dados são processados e transferidos em pequenas partes. Portanto, você pode processar uma grande quantidade de dados sem usar muita memória a qualquer momento.

Vamos ver como podemos usar o spawn() para criar um processo filho. Vamos escrever um novo módulo Node.js que cria um processo filho para executar o comando find. Usaremos o comando find para listar todos os arquivos no diretório atual.

Crie um novo arquivo chamado findFiles.js:

  • nano findFiles.js

No editor de texto, comece chamando o comando spawn():

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

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

Primeiro importamos a função spawn() a partir do módulo child_process. Em seguida, chamamos a função spawn() para criar um processo filho que executa o comando find. Mantemos a referência ao processo na variável child, que usaremos para ouvir os eventos transmitidos dela.

O primeiro argumento no spawn() é o comando que será executado, neste caso, o find. O segundo argumento é um array que contém os argumentos para o comando executado. Neste caso, estamos dizendo ao Node.js para executar o comando find com o argumento ., fazendo com que o comando encontre todos os arquivos no diretório atual. O comando equivalente no terminal é find ..

Com as funções exec() e execFile(), escrevemos os argumentos juntamente com o comando em uma string. No entanto, com spawn(), todos os argumentos para comandos devem ser inseridos no array. Isso ocorre porque o spawn(), ao contrário do exec() e do execFile(), não cria um novo shell antes de executar um processo. Para ter comandos com argumentos em uma string, você precisa do Node.js para criar um novo shell também.

Vamos continuar nosso módulo adicionando ouvintes para a saída do comando. Adicione as linhas destacadas a seguir:

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

Os comandos podem retornar dados tanto na stream stdout quanto na stream stderr. Portanto, você adicionou ouvintes para ambas. Você pode adicionar ouvintes chamando o método on() dos objetos de cada stream. O evento data das streams nos dá a saída do comando para essa stream. Sempre que obtemos dados em qualquer uma das streams, nós os registramos no console.

Em seguida, ouvimos outros dois eventos: o evento error, se o comando falhar ao executar ou for interrompido, e o evento close, para quando o comando tiver terminado a execução, fechando assim a stream.

No editor de texto, complete o módulo Node.js escrevendo as seguintes linhas destacadas:

~/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 os eventos error e close, você configurou um ouvinte diretamente na variável child. Ao ouvir eventos error, se um erro ocorrer, o Node.js fornecerá um objeto Error. Neste caso, você registra a propriedade message do erro.

Ao ouvir o evento close, o Node.js fornece o código de saída do comando. Um código de saída denota se o comando funcionou ou não. Quando um comando é executado sem erros, ele retorna o valor mais baixo possível para um código de saída: 0. Quando executado com um erro, ele retorna um código não-zero.

O módulo está completo. Salve e saia do nano com CTRL+X.

Agora, execute o código com o comando node:

  • node findFiles.js

Depois de terminar, você encontrará a seguinte saída:

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

Encontramos uma lista de todos os arquivos em nosso diretório atual e o código de saída do comando, que é 0, uma vez que ele executou com sucesso. Embora nosso diretório atual tenha um pequeno número de arquivos, se executássemos esse código em nosso diretório home, o programa listaria todos os arquivos em cada pasta acessível ao usuário. Como ele tem uma saída potencialmente grande, usar a função spawn() é ideal, pois suas streams não exigem tanta memória quanto um buffer grande.

Até agora, usamos funções para criar processos filhos para executar comandos externos no sistema operacional. O Node.js também fornece uma maneira de criar um processo filho que executa outros programas do Node.js. Vamos usar a função fork() para criar um processo filho para um módulo Node.js na próxima seção.

Passo 3 — Criando um processo filho com fork()

O Node.js fornece a função fork(), uma variação da função spawn(), para criar um processo filho que também é um processo Node.js. O principal benefício do uso do fork() sobre o spawn() ou exec() para criar um processo Node.js é que o fork() permite a comunicação entre o processo pai e o filho.

Com o fork(), além de recuperar dados do processo filho, um processo pai pode enviar mensagens para o processo filho em execução. Da mesma forma, o processo filho pode enviar mensagens para o processo pai.

Vamos ver um exemplo onde usar o fork() para criar um novo processo filho do Node.js pode melhorar o desempenho da nossa aplicação. Os programas Node.js são executados em um único processo. Portanto, as tarefas de uso intensivo de CPU, como iterar sobre loops grandes ou analisar arquivos JSON grandes impede a execução de outro código JavaScript. Para certas aplicações, essa não é uma opção viável. Se um servidor Web está bloqueado, então ele não pode processar nenhuma nova requisição de entrada até que o código que está bloqueando tenha concluído sua execução.

Vamos ver isso na prática criando um servidor Web com dois endpoints. Um endpoint fará uma computação lenta que bloqueia o processo do Node.js. O outro endpoint irá retornar um objeto JSON dizendo hello.

Primeiro, crie um novo arquivo chamado httpServer.js, que terá o código para nosso servidor HTTP:

  • nano httpServer.js

Vamos começar configurando o servidor HTTP. Isso envolve a importação do módulo http, criação de uma função de ouvinte de requisição, criação de um objeto server, e a escuta de requisições no objeto server. Se você quiser mergulhar mais profundamente na criação de servidores HTTP no Node.js ou se gostaria de uma atualização, leia nosso guia How To Create a Web Server in Node.js with the HTTP Module.

Digite o código a seguir em seu editor de texto para configurar um 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 um servidor HTTP que será executado em http://localhost:8000. Ele usa template literals para gerar dinamicamente essa URL.

Em seguida, vamos escrever uma função intencionalmente lenta que conta em um loop 5 bilhões de vezes. Antes da função requestListener(), adicione o seguinte 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) {};
...

Isso usa a sintaxe da função de seta para criar um loop while que conta até 5000000000.

Para completar este módulo, precisamos adicionar código à função requestListener(). Nossa função irá chamar a slowFunction() no subpath e retornar uma pequena mensagem JSON para o outro. Adicione o código a seguir ao 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"}`);
  }
};
...

Se o usuário chegar até o servidor no subpath /total, então executamos slowFunction(). Se somos acessados no subpath /hello, retornamos esta mensagem JSON: {"message":"hello"}.

Salve e saia do arquivo pressionando CTRL+X.

Para testar, execute este módulo de servidor com node:

  • node httpServer.js

Quando o servidor iniciar, o console exibirá o seguinte:

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

Agora, para testar o desempenho do nosso módulo, abra dois terminais adicionais. No primeiro terminal, use o comando curl para fazer uma requisição para o endpoint, /total, que esperamos ser lento:

  • curl http://localhost:8000/total

No outro terminal, use o curl para fazer uma requisição para o endpoint /hello como esta:

  • curl http://localhost:8000/hello

A primeira requisição retornará o seguinte JSON:

Output
{"totalCount":5000000000}

A segunda requisição irá retornar este JSON:

Output
{"message":"hello"}

A requisição para /hello finaliza apenas após a requisição para /total. O slowFunction() bloqueou a execução de todos os outros códigos enquanto ainda estava em seu loop. Verifique isso olhando para a saída do servidor Node.js que foi registrada em seu terminal original:

Output
Returning /total results Returning /hello results

Para processarmos o código que está bloqueando ao mesmo tempo em que aceitamos requisições de entrada, podemos mover o código bloqueante para um processo filho com fork(). Vamos mover o código bloqueante para seu próprio módulo. O servidor Node.js então criará um processo filho quando alguém acessar o endpoint /total e ouvir os resultados deste processo filho.

Refatore o servidor criando primeiro um novo módulo chamado getCount.js que conterá slowFunction():

  • nano getCount.js

Agora, digite o código para slowFunction() mais uma vez:

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

  return counter;
}

Como este módulo será um processo filho criado com fork(), também podemos adicionar código para se comunicar com o processo pai quando slowFunction() tiver finalizado o processamento. Adicione o bloco de código a seguir que envia uma mensagem para o processo pai com o JSON para retornar ao usuário:

~/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 quebrar esse bloco de código. As mensagens entre um processo pai e o filho criado pelo fork() são acessíveis através do objeto process global do Node.js. Adicionamos um ouvinte à variável process para procurar eventos message. Após receber um evento message , verificamos se ele é o evento START. Nosso código de servidor enviará o evento START quando alguém acessar o endpoint /total. Ao receber esse evento, executamos slowFunction() e criamos uma string JSON com o resultado da função. Usamos process.send() para enviar uma mensagem para o processo pai.

Salve e saia do getCount.js pressionando CTRL+X no nano.

Agora, vamos modificar o arquivo httpServer.js para que, ao invés de chamar slowFunction(), ele crie um processo filho que executa getCount.js.

Abra novamente httpServer.js com o nano:

  • nano httpServer.js

Primeiro, importe a função fork() a partir do módulo child_process:

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

Em seguida, vamos remover o slowFunction() deste módulo e modificar a função requestListener() para criar um processo filho. Mude o código em seu arquivo para que fique assim:

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

Quando alguém vai até o endpoint /total, agora criamos um novo processo filho com fork(). O argumento do fork() é o caminho para o módulo Node.js. Neste caso, ele é o arquivo getCount.js em nosso diretório atual, que recebemos a partir de __dirname. A referência a este processo filho é armazenada em uma variável. child.

Em seguida, adicionamos um ouvinte ao objeto child. Este ouvinte captura todas as mensagens que o processo filho nos dá. Neste caso, getCount.js retornará uma string JSON com o número total contado pelo loop while. Quando recebemos essa mensagem, enviamos o JSON para o usuário.

Usamos a função send() da variável child para exibir uma mensagem. Este programa envia a mensagem START, que começa a execução de slowFunction() no processo filho.

Salve e saia do nano pressionando CTRL+X.

Para testar a melhoria usando o fork() feito no servidor HTTP, comece executando o arquivo httpServer.js com node:

  • node httpServer.js

Como antes, ele irá exibir a seguinte mensagem quando for iniciado:

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

Para testar o servidor, vamos precisar de dois terminais adicionais como fizemos da primeira vez. Você pode reutilizá-los se eles ainda estiverem abertos.

No primeiro terminal, use o comando curl para fazer uma requisição para o endpoint, /total, que leva um tempo para ser calculado:

  • curl http://localhost:8000/total

No outro terminal, use o curl para fazer uma requisição para o endpoint /hello, que responde em pouco tempo:

  • curl http://localhost:8000/hello

A primeira requisição retornará o seguinte JSON:

Output
{"totalCount":5000000000}

A segunda requisição retornará este JSON:

Output
{"message":"hello"}

Ao contrário da primeira vez que tentamos isso, a segunda requisição para /hello executa imediatamente. Você pode confirmar revisando os logs, que se parecerão com este:

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

Esses logs mostram que a requisição para o endpoint /hello foi executada após o processo filho ser criado, mas antes que o processo filho tivesse terminado sua tarefa.

Uma vez que movemos o código bloqueante para um processo filho usando fork(), o servidor ainda foi capaz de responder a outras requisições e executar outro código JavaScript. Devido à capacidade de passagem de mensagem da função fork(), podemos controlar quando um processo filho começa uma atividade e retornar dados de um processo filho para um processo pai.

Conclusão

Neste artigo, você usou várias funções para criar um processo filho no Node.js. Primeiramente você criou processos filhos com exec() para executar comandos do shell a partir do código Node.js. Em seguida, você executou um arquivo executável com a função execFile(). Você viu a função spawn(), que também pode executar comandos mas retorna dados através de uma stream e não inicia um shell como fazem exec() e execFile(). Finalmente, você usou a função fork() para permitir uma comunicação bidirecional entre o processo pai e o filho.

Para aprender mais sobre o módulo child_process, leia a documentação do Node.js. Se quiser continuar aprendendo sobre o Node.js, retorne para a série Como programar em Node.js, ou pesquise por projetos de programação e configurações em nossa página de tópicos do Node.

Creative Commons License