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 thestdout
of the child process, which is aReadable
stream. Wheneverls
, the spawn, writes a data chunk to itsstdout
, 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 whenls
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 as0
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 ofexecFile
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 fromspawn
: after exit, any output from the spawned process (whether fromstdout
orstderr
) 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
orstderr
is null), another call toexecFile
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!💜