NodeJS: How to await an asynchronous child process

TL;DR: the solution.

If you need a command to happen after another, you can use spawnSync and other *Sync functions in the child_process module. However, the command's output will not be printed to the terminal as it runs: you might decide to read the return value and send the command's output to your program's output yourself, but all output will be bunched up and printed at once.

const { execSync } = require("child_process");
const process = require("process");

process.stdout.write(execSync("echo one; sleep 1; echo two; sleep 1").toString())
console.log("This must happen last.");

For reference, this is what I want to happen:

echo one
sleep 1
echo two
sleep 1
echo "This must happen last."

To actually send the child process's output to your program's output as they come, you have to use the asynchronous version and attach a handler on it. But now things get out of order.

const { spawn } = require("child_process");
const process = require("process");

function cmd(...command) {
  let p = spawn(command[0], command.slice(1));
  p.stdout.on("data", (x) => {
    process.stdout.write(x.toString());
  });
  p.stderr.on("data", (x) => {
    process.stderr.write(x.toString());
  });
}

cmd("bash", "-c", "echo one; sleep 1; echo two; sleep 1");
console.log("This must happen last.");

The solution is to wrap it in a promise and use await. (If you're using .mjs you can use await without being in an async function.)

const { spawn } = require("child_process");
const process = require("process");

function cmd(...command) {
  let p = spawn(command[0], command.slice(1));
  return new Promise((resolveFunc) => {
    p.stdout.on("data", (x) => {
      process.stdout.write(x.toString());
    });
    p.stderr.on("data", (x) => {
      process.stderr.write(x.toString());
    });
    p.on("exit", (code) => {
      resolveFunc(code);
    });
  });
}

async function main() {
  await cmd("bash", "-c", "echo one; sleep 1; echo two; sleep 1");
  console.log("This must happen last.");
}

main();