Going from Node.js to Deno: Part 5 — Child process
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.mp4Child 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
That’s all about child process handling. The other parts in this series are: