Node.js Child Processes
The child_process
module, a core Node.js module, spawns child processes to execute shell commands, run executable files, and start other programs. This lets multiprocessing and application speed improve by handling long-running or CPU-intensive tasks without stopping Node.js’s main thread. The kernel handles such work, allowing Node.js to focus on other tasks.
Here are crucial child_process
module methods:
Executing Commands
The exec()
function runs shell commands and stores their output. It starts a shell process and runs the command.
Syntax and Parameters: child_process.exec(command[, options][, callback])
- command: A required string containing the shell command to be executed, which can include shell piping or redirection.
- options: An optional object that allows customisation of the execution. Key options include:
encoding
: Default is'utf8'
.timeout
: Maximum milliseconds to wait before killing the process.maxBuffer
: Maximum amount of data (in bytes) allowed on stdout or stderr before the process is killed (default 200KB).killSignal
: The signal sent to the child process after a timeout (default'SIGTERM'
).cwd
: Sets the current working directory for the child process.env
: An object of key-value pairs for environment variables.shell
: The shell to execute the command with (default/bin/sh
on UNIX,cmd.exe
on Windows).uid
andgid
: User and group identity for the process, in terms of standard system permissions.
- callback: An optional function that is invoked when the command finishes. It receives three arguments:
err
: AnError
object if the command failed to run.stdout
: ABuffer
object (or string, depending on encoding) containing the command’s standard output.stderr
: ABuffer
object (or string) containing the command’s standard error output.
Synchronous Version: Synchronous execution is possible with execSync()
. If a callback is received, it blocks the main thread and throws an error; otherwise, it returns the standard output immediately.
Example: Listing files in a directory
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}`);
});
Output:
stdout:
total 4.0K
-rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js
This output lists the contents of the directory in a long format.
Running Executable Files
To launch an executable file directly, use the execFile()
function. In contrast to exec()
, it usually doesn’t create a shell, which makes it marginally more efficient. When certain executables need to be run without the use of shell features like piping or redirection, this function is perfect.
Syntax and Parameters: child_process.execFile(file[, args][, options][, callback])
- file: Where the executable file is located.
- args: An optional array of arguments to provide to the executable is called
args
. A significant distinction fromexec()
, where parameters are a part of the command string, is this. callback
andoptions
: Very similar to those forexec()
.
Synchronous Version: The synchronous version is execFileSync()
.
Note on Windows Scripts: execFile()
does not construct a shell, hence it cannot execute batch (.bat
) or command (.cmd
) files on Windows. exec()
or spawn()
should be used for these.
Example: Running a bash script to download and Base64 encode an image
const { execFile } = require('child_process');
const path = require('path');
execFile(path.join(__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}`);
});
Output (simplified for brevity):
stdout:
... (Base64 encoded image data) ...
The shell script’s successful execution as a child process is demonstrated by this.
Spawning Processes
Using a command and array of arguments, spawn()
begins a new process and streams unbuffered output. This is ideal for commands that produce a lot of data or run for a long time because data is processed in chunks rather than held in memory.
Syntax and Parameters: child_process.spawn(command[, args][, options])
- command: The command that has to be carried out.
- args: The command’s array of parameters.
- options: An optional object for configuration. It also supports
cwd
,env
,uid
,gid
options similar toexec()
. A notable option isstdio
, which defines the child’s input/output streams.
Interacting with Streams: The spawn()
method yields an instance of ChildProcess
, which exposes stream-based stdout
and stderr
stream.Readable
examples. These streams allow you to manage the output as it becomes available by attaching event listeners. Frequent occurrences include:
- ‘data’: Released within the stream when data is accessible.
- ‘error’: Issued in the event that the command is interrupted or does not execute.
- ‘close’: The
'close'
command is released once the child process is finished.
Synchronous Version: The synchronous version is spawnSync()
.
Example: Finding files in the current directory
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}`);
});
Output:
stdout:
.
./listFiles.js
./getNodejsImage.js
./processNodejsImage.sh
./nodejs-logo.svg
child process exited with code 0
This output shows find
command listing files and folders, with messages logged from both stdout
and close
events.
Forking Node.js Processes
A specialised version of spawn()
, fork()
is intended just for spawning new Node.js processes. The built-in communication channel that allows bidirectional messaging between the parent and child processes is its main benefit. Coordinated execution is made possible by this, as the parent can communicate with the child and the youngster can communicate with the parent.
Syntax and Parameters: child_process.fork(modulePath[, args][, options])
- modulePath: The path to the Node.js program to be executed in the child process.
args
: An optional array of arguments, accessible viaprocess.argv
in the child process.options
: An optional object, includingcwd
,env
,encoding
,execPath
, and notably,silent
. Settingsilent: true
disables the child’s stdio from being associated with the parent’s.
Communication: The global process
object is used for communication between the parent and the kid.
- Parent to Child:
child.send(message)
. - Child to Parent:
process.send(message)
. On('message', callback)
allows both parent and child to listen for'message'
events.
Important Note: A forked child process must explicitly execute process.exit()
when its job is finished; it does not automatically terminate when it is finished.
Example: Offloading a slow, blocking function to a child process
getCount.js (Child Process):
const slowFunction = () => {
let counter = 0;
while (counter < 5000000000) { // Simulates a long-running task
counter++;
}
return counter;
};
process.on('message', (message) => {
if (message === 'START') {
console.log('Child process received START message');
let slowResult = slowFunction();
let responseMessage = `{"totalCount":${slowResult}}`;
process.send(responseMessage); // Send result back to parent
}
});
httpServer.js (Parent Process):
const http = require('http');
const { fork } = require('child_process');
const host = 'localhost';
const port = 8000;
const requestListener = function (req, res) {
if (req.url === '/total') {
const child = fork(__dirname + '/getCount'); // Spawn child process
child.on('message', (message) => {
console.log('Returning /total results');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(message);
});
child.send('START'); // Send message to child to start computation
} else if (req.url === '/hello') {
console.log('Returning /hello results');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(`{"message":"hello"}`);
}
};
const server = http.createServer(requestListener);
server.listen(port, host, () => {
console.log(`Server is running on http://${host}:${port}`);
});
The /hello
response is not blocked by the slowFunction
executing in the child process, which logs its message independently, when you execute httpServer.js
and hit the /total
and /hello
destinations simultaneously. This illustrates how fork()
makes CPU-bound processes concurrent.
Output from httpServer.js terminal:
Server is running on http://localhost:8000
Child process received START message
Returning /hello results
Returning /total results
(The crucial point is that /hello
does not wait for /total
to complete its laborious computation; the precise sequence of “Returning /hello results” and “Child process received START message” may differ slightly due to their asynchronous nature.)