jsrosetta

I/O

Running Subprocesses (exec)

How Node.js's child_process.execSync/exec compares to os/exec (Go), std::process::Command (Rust), Process (Swift), and ProcessBuilder (Java).

Minimum versions
Node.js ≥ 14.13.1Go ≥ 1.7Rust ≥ 1.71Swift ≥ 5.7Java ≥ 25
Verified on
Node.js 24.12.0Go 1.27.1Rust 1.98.1Swift 6.2.4Java 25.0.4.1

Node.js code is an ES module: save it as .mjs or set "type": "module" in package.json.

Running a system command comes in two flavors: wait for it to finish before continuing (sync), or run it without blocking and handle the result later (async). Go doesn't have that distinction: cmd.Run() always blocks the calling goroutine. Rust's std is the same — Command::output() blocks the current thread; real "async" needs tokio::process::Command (not in std). Swift's Process also only has a synchronous API (waitUntilExit()); an "async with timeout" has to be assembled by hand with Task. Java ships both: Process.waitFor() (blocking) and Process.onExit(), which returns a CompletableFuture<Process>.

Running synchronously

import { execSync } from 'node:child_process'
 
const output = execSync(`echo 'hello world'`)
 
console.log(output.toString())
hello world

Running asynchronously with a timeout

import { exec } from 'node:child_process'
import { promisify } from 'node:util'
 
const execAsync = promisify(exec)
 
const { stdout, stderr } = await execAsync(`echo 'hello world'`, { timeout: 5000 })
 
if (stderr) {
  console.error(stderr)
}
 
console.log(stdout)
hello world

Reference: github.com/miguelmota/golang-for-nodejs-developers#exec-sync