Want to lay eggs? JS got you covered.

Intro to Spawning in Node.js

Want to lay eggs? JS got you covered.

This is an attempt to provide you with a “quick and dirty guide to spawn through Node.js.” So, come along, let's lay some eggs. 😉


Let me quickly clarify a couple of terms.

In computer science, "process" refers to a program in execution. This means that Node.js, our favorite server-side environment, becomes a process when we do something like node test.js.

That being said, in laymen's terms, "spawn" means to lay or release eggs. This has a direct parallel in the programming world, where one process can give birth to another process (the egg), and the technique involved (surprise!) is called spawning.

How?

The node module child_process is the protagonist here. It provides facilities to spawn subprocesses, primarily via its spawn function. Spawn means to create/launch a new process from within Node—a process itself. This new process, referred to as a subprocess, is a separate entity, but the parent process (Node) can interact with it via streams such as stdout.

An example will be apt for your comprehension. So, let me spawn ls, a Bash program, using Node. Just to refresh your memory, ls, when executed without any path argument, lists all the files in the current working directory.

import { spawn } from "child_process";

const process = spawn('ls', ['-l']);

console.log(`Spawned Process PID: ${process.pid}`);

process.stdout.on('data', (data) => {
    console.log(`Output:\n${data}`);
});

process.stderr.on('data', (data) => {
    console.log(`Error:\n${data}`);
});

// Handle errors while spawning
process.on('error', (err) => {
    console.log(`Error:\n${err}`);
});

process.on('close', (code) => {
    console.log(`Process exited with code ${code}`);
});

If everything goes right, you should see something similar to the following on your console:

Spawned Process PID: 2287
Output:
total 48
-rw-r--r-- 1 atanu atanu    77 Jan  8 19:21 1.c
-rw-r--r-- 1 atanu atanu   449 Jan 11 17:35 1.js
-rw-r--r-- 1 atanu atanu  1019 Jan  8 19:04 2.js
-rw-r--r-- 1 atanu atanu   508 Jan  8 11:09 3.js
-rw-r--r-- 1 atanu atanu   324 Jan  8 19:18 4.js
-rw-r--r-- 1 atanu atanu  1606 Jan  8 20:27 5.js
-rwxr-xr-x 1 atanu atanu 15960 Jan  8 19:03 a.out
-rw-r--r-- 1 atanu atanu    24 Jan  8 09:19 package.json
drwxr-xr-x 7 atanu atanu  4096 Jan  8 19:36 workspaces

Process exited with code 0

The list of files above gives the impression of someone typing ls -l directly into the shell, but that's the coolest part: it's the output of a JavaScript program! 😎

What is going on?

The spawn function receives two inputs. The first, a string, is the name of the program to be spawned, i.e., ls. The second one is an array that contains the command-line argument that should be passed to ls.

Although spawn is an asynchronous function, it immediately returns an object—a ChildProcess instance—that allows for registering listener functions. These event listeners are triggered for specific "events" during the life cycle of the spawned process.

  • process.stdout refers to the stdout of the child process, which is a Readable stream. Whenever ls, the spawn, writes a data chunk to its stdout, the stream emits a 'data' event. The corresponding listener is then invoked with the chunk, which is logged to the console in the above example.

  • Similarly, the stderr stream emits a 'data' event when ls writes an error message to it, calling the respective listener with the error data that gets logged to the console as shown below:

      Spawned Process PID: 6136
      Error:
      ls: invalid option -- 'e'
      Try 'ls --help' for more information.
    
      Process exited with code 2
    
  • Observe the last line in the above console outputs. Regardless of whether the process produces the desired output or an error message, when the spawned process exits, it emits a 'close' event. This event triggers the attached listener with the process's exit code, such as 0 for successful completion.

Note: A ChildProcess instance extends the EventEmitter class for us to be able to pull off such event-driven stunts. Don’t know what I’m talking about? 😕 Check out my blogs on Events and Inheritance for a quick refresher.

A Shortcut

The child_process module has some alternatives based on the spawn function. execFile is one of them, which uses the traditional callback approach instead of streams. Allow me to offer an example.

The following snippet uses execFile to spawn gcc that compiles a C program. If the compile process exits successfully, it spawns the resultant object code:

import { execFile } from "child_process";

execFile('gcc', ['1.c'], (err, stdout, stderr) => {
    if (err) return console.error(`Spawning Error:\n${err}`);

    if (stderr) return console.error(`Error:\n${stderr}`);

    return execFile('./a.out', (err, stdout, stderr) => {
        if (err) return console.error(`Spawning Error:\n${err}`);

        if (stderr) return console.error(`Error:\n${stderr}`);

        return console.log(`Output:\n${stdout}`);
    });
});

Explanation:

  • Similar to spawn, the first two arguments of execFile are the name of the program to be spawned, gcc, and the necessary command-line argument, which is the name of the source file, 1.c.

  • Its third argument is a callback that is invoked after the subprocess exits. This is where execFile differs from spawn: after exit, any output from the spawned process (whether from stdout or stderr) is buffered in the memory of the Node.js process and passed to this callback in one go.

  • In our example, once the first execFile call completes and there are no errors (err or stderr is null), another call to execFile spawns the ./a.out process, which executes the compiled object code. If everything goes well, your console should look like:

      Output:
      Hello World
    

FYI, we just compiled a freakin' C program using JavaScript and executed the object code. A big round of applause for us. 👏

THE END.


Immensely grateful for your time. I truly hope I was able to offer you something of value. See you soon!

Adios!💜