A high level view
Node.js provides 2 lower level functions for spawning child processes:
child_process.spawn()
child_process.spawnSync()
As you can see in its name, the first one is for async operaitions and second one is for synchronous operations.
A few high level apis built upon these 2 low level api, which you can see in below list:
child_process.spawn()
child_process.exec()
child_process.execFile()
child_process.fork()
child_process.spawnSync()
child_process.execSync()
child_process.execFileSync()
Now let's see these apis one by one.
exec
Function exec will create a shell then excutes the command passed in. Then we can get the command output from callback function.
const child_process = require("child_process");
child_process.exec("ls -l", function (error, stdout, stderr) {
if (error) {
console.error(error.toString());
} else if (stderr !== "") {
console.error(stderr);
} else {
console.log(stdout);
}
});
A few options can be passed in. For example, below we pass a signal to abort the subprocess.
const controller = new AbortController();
const { signal } = controller;
const child = exec('grep ssh', { signal }, (error) => {
console.log(error); // an AbortError
});
controller.abort();
execFile
The main difference is between execFile and exec is that, execFile will not spawn a shell by default. Rather, the specified executable file is spawned directly as a new process making it slightly more efficient than exec.
child_process.execFile(
"ls",
["-l", "/"],
function (error, stdout, stderr) { }
);
fork
The method fork is used specifically to spawn a new Node.js processes.
After new Node.js process created, an IPC communication channel is established, which allows communications between them.
Below example show how to send/receive messages between main process and subprocess.
// index.js
const child_process = require("child_process");
const child = child_process.fork(
__dirname + "/child.js", // js file
["-foo"], // pass in paramters
{
env: { bar: "baz" } // env
}
);
child.on("message", function (message) {
console.log("parent received: " + message.count);
if (child.connected) {
message.count++;
child.send(message);
}
});
child.send({ count: 0 });
// child.js
console.log("argv: " + process.argv);
console.log("env: " + JSON.stringify(process.env, null, 2));
process.on("message", function (message) {
console.log("child received: " + message.count);
if (process.connected) {
message.count++;
process.send(message);
}
});
spawn
Now comes the function spawn, which is the low level api, and all above 3 apis calls spawn internally.
Below example shows the basic usage of spawn.
const child_process = require("child_process");
const ls = child_process.spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
ls.on('error', err => {
console.error(err);
});
A lot of options can be used in spawn
function, but one option called stdio
is important to be noted.
Option stdio
is usd to configure the comunication between parent and child processes. By default, child process's stdin, stdout and stderr are redirected to corresponding subprocess.stdin/stdout/stderr
event, which you can see in above example. If you set option stdio
as value ipc
, then an IPC channel will created for parent/child communication, which you already seen in fork
function example.