Spawning Child Process in Node.js

Spawning Child Process in Node.js

·

3 min read

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.