Node.js Child Processes using spawn, exec, fork & async/await
Node.js runs in a single thread. You can, however take advantage of multiple processes.
child_process module allows to create child processes in Node.js. Those
processes can easily communicate with each other using a built-in messaging system.
There are four different ways to create a child process in Node: spawn(), fork(), exec(), and execFile().
spawn launches a command in a new process:
const { spawn } = require('child_process')
const child = spawn('ls', ['-a', '-l']);You can pass arguments to the command executed by the spawn as array using its second argument.
spawn returns a ChildProcess object. As ChildProcess inherits from EventEmitter you can register handlers for
events on it.
child.on('exit', code => {
console.log(`Exit code is: ${code}`);
});Apart from exit event, there are also disconnect, error, close and
message events.
message event allows for the caller/parent to communicate with the child
process. This event is emitted when child process uses process.send().
Child processes have three standard IO streams available: stdin (writeable),
stdout (readable) and stderr (readable). Streams also inherit from
EventEmitter. On readable streams there is data event emitted when a
commands run inside a child process outputs something.
// Async Iteration available since Node 10
for await (const data of child.stdout) {
console.log(`stdout from the child: ${data}`);
};Since stdin of the main process is a readable stream, you can pipe it into
the stdin of the child process (which is a writeable stream).
const { spawn } = require('child_process')
const child = spawn('wc');
process.stdin.pipe(child.stdin)
for await (const data of child.stdout) {
console.log(`stdout from the child: ${data}`);
};You can also pass the output of one child process as the input to the another child process.
const { spawn } = require('child_process')
const find = spawn('find', ['.', '-type', 'f']);
const wc = spawn('wc', ['-l']);
find.stdout.pipe(wc.stdin);
for await (const data of wc.stdout) {
console.log(`number of files: ${data}`);
};spawn doesn't create a shell to execute the command while exec does create a
shell. Thus, it's possible to specify the command to execute using the shell
syntax. exec also buffers the command's entire output instead of using a stream.
const util = require('util');
const exec = util.promisify(require('child_process').exec);
async function main() {
const { stdout, stderr } = await exec('find . -type f | wc -l');
if (stderr) {
console.error(`error: ${stderr}`);
}
console.log(`Number of files ${stdout}`);
}
main()You can force spawn to create a shell using shell: true option.
const { stdout, stderr } = await exec('find . -type f | wc -l', { shell: true });spawn can also directly use IO streams of the parent/caller by specifying
stdio: inherit option. This way the output from the script will be displayed
immediately.
You can specify a directory to use for the command being executed by spawn
using cwd option.
You can also pass shell variables to the child process using env option. The
child process won't have access to environment variables of parent/caller.
const child = spawn('echo $PYTHON_PYTH', {
env: { PYTHON_PATH: '/usr/bin/python' },
});fork is a variation of spawn where both the parent/caller and the child
process can communicate with each other via send().
Thanks to fork, computation intensive tasks can be separated from the main
event loop.
In the example below, the server won't be blocked by the computation
intensive task triggered by /compute route. The task will be handled by
another Node process. Once it finished, the result will be send back to the
server so that it can be then returned over HTTP as a response.
const longComputation = () => {
let sum = 0;
for (let i = 0; i < 1e9; i++) {
sum += i;
};
return sum;
};
process.on('message', message => {
const result = longComputation();
process.send(result);
});const http = require('http');
const { fork } = require('child_process');
const server = http.createServer();
server.on('request', (request, response) => {
if (request.url === '/compute') {
const compute = fork('compute.js');
compute.send('start');
compute.on('message', result => {
res.end(`Long computation result: ${result}`)
});
} else {
res.end('Route not found')
}
});
server.listen(3000);