Going from Node.js to Deno: Part 5 — Child process

Mayank C
Tech Tonic

--

Node.js is a 13 year old, very popular server side runtime that runs JavaScript code in the backend. Deno is a 4 year old, relatively new runtime that runs JavaScript and TypeScript code in the backend. There are similarities and differences between Deno and Node.js.

Considering the extreme popularity of Node.js and the fact that Deno is slowly gaining grounds, in the near future, many developers may need to transition from Node.js to Deno, or work on both the technologies together. Either way, there will be a need to learn or transition to Deno.

In this series of articles (part 1, part 2, part 3, part 4, part 6, part 7, part 8, part 9), we’ll provide a comprehensive guide for Node.js developers who are interested in learning and working on Deno.

Spawn a child process

Node.js

In Node.js, the core child_process module can be used to spawn a child process, collect the output, and pass messages back and forth. The child_process module comes with a spawn API that runs any arbitrary child process and streams child process’s output and error to the parent.

import { spawn } from "node:child_process";spawn("ls", ["-ltr", "./testdata/*.mp4"]);

The above code starts any arbitrary child process.

Deno

Deno’s core runtime comes with a similar but unstable API spawn, that can be used to spawn an arbitrary child process. No imports are required to use this API.

await Deno.spawn("ls", { args: ["-ltr", "./testdata/*.mp4"] });

Note: The spawn API needs allow-run permission and is unstable.

Collect output from child process

Node.js

The spawn API in Node.js implements the EventEmitter interface. It sends events in cases like new data, child process exits, etc. All the events needs to be handled with callbacks.

import { spawn } from "node:child_process";const child = spawn("ls", [
"-ltr",
"/var/tmp/testdata/someDir",
]);
child.stdout.on(
"data",
(data) => console.log("Data received:", data.toString()),
);
child.stderr.on(
"data",
(data) => console.log("Error received:", data.toString()),
);
child.on("close", () => console.log("Child completed"));

A quick run of the above code:

> node app.mjs 
Data received: total 198272
drwxr-xr-x 4 mayankc staff 128 Nov 21 2021 someDir2
-rw-r--r--@ 1 mayankc staff 100450390 Jul 3 22:07 longVideo.mp4
-rw-r--r--@ 1 mayankc staff 1057149 Jul 3 22:07 shortVideo.mp4
Child completed

The other way in Node.js is to use exec API which can run any command in a new shell:

import { exec } from "node:child_process";exec("ls -ltr /var/tmp/testdata/someDir", (err, stdout, stderr) => {
console.log("Data received:", stdout.toString());
});

Deno

The spawn API in Deno runs to completion and then returns stdout & stderr data to the caller. The behavior is similar to Node.js’s exec command. All of this in a single line of code.

const { stdout: childOutput } = await Deno.spawn("ls", {
args: ["-l", "/var/tmp/testdata/someDir"],
});
console.log("Data received:", new TextDecoder().decode(childOutput));

A quick run of the above code:

> deno run --allow-run --unstable app.ts 
Data received: total 198272
-rw-r--r--@ 1 mayankc staff 100450390 Jul 3 22:07 longVideo.mp4
-rw-r--r--@ 1 mayankc staff 1057149 Jul 3 22:07 shortVideo.mp4
drwxr-xr-x 4 mayankc staff 128 Nov 21 2021 someDir2

Communicate with child process

Node.js

A child process created using fork API has an IPC (Inter process communication) setup between parent and child. The parent process can send messages to the child anytime. The parent process can also subscribe to receive messages from the child anytime. The same is true with child process too.

parent.mjs

import { fork } from "node:child_process";
const child = fork("./child.mjs");
child.on("message", (msg) => {
console.log("PARENT: received message from child", msg.data);
});
child.send({ data: "Hello from parent" });

child.mjs

process.on("message", (msg) => {
console.log("CHILD: received message from parent", msg.data);
});
process.send({ data: "Hello from child" });

A quick run:

> node parent.mjs 
CHILD: received message from parent Hello from parent
PARENT: received message from child Hello from child

Deno

Deno supports web standard web workers APIs to create an IPC between parent and child process. A web worker is just like a child process with its own V8 instance. The parent process can send messages to the child anytime. The parent process can also subscribe to receive messages from the child anytime. The same is true with child process too.

parent.ts

const child = new Worker(new URL("./child.ts", import.meta.url).href, {
type: "module",
});
child.onmessage = (evt) => {
console.log("PARENT: received message from child", evt.data);
};
child.postMessage("Hello from parent");

child.ts

self.onmessage = (evt) => {
console.log("CHILD: received message from parent", evt.data);
};
self.postMessage("Hello from child");

A quick run:

> deno run --allow-read parent.ts 
PARENT: received message from child Hello from child
CHILD: received message from parent Hello from parent

--

--