Michael
Michael

Reputation: 7113

How to use node's child_process.exec() with promises

I try to execute long processes sequentially with node.js (docker exec commands).

I do:

const childProcess = require('child_process');

const execWithPromise = async command => {
    return new Promise(async resolve => {
        const process = childProcess.exec(command);

        process.on('exit', err => resolve(err));
        process.on('close', err => resolve(err));
    });
};

const run = async () => {
    await execWithPromise('/usr/local/bin/docker exec -i -t cucumber node long-running-script.js');
    await execWithPromise('/usr/local/bin/docker exec -i -t cucumber node long-running-script.js');
};

run();

But the promise is resolved immediately with a result of 1. In both cases. The command runs on the commandline just fine.

Why is it returning immediately?

Upvotes: 4

Views: 14198

Answers (2)

bloo
bloo

Reputation: 1550

Here's an answer for current node.js:

TypeScript

For TypeScript, in app.ts:

import { exec as lameExec } from "child_process";
const exec = promisify(lameExec);
const { stdout, stderr } = await exec(`some command to run`);

You can then run this using esrun (npm i @digitak/esrun) which supports top level await:

npx esrun app.ts

JavaScript

In JS call the file app.mjs and run it with

node app.mjs

Upvotes: 3

Peter Grainger
Peter Grainger

Reputation: 5097

child_process.exec expects a callback as the second or third argument. It doesn't return a promise. You have a few choices depending on your use case and version of node. The following work with node 16.x

Use a callback and return the resolve.

const execWithPromise = command =>
  new Promise((resolve, reject) => {
    childProcess.exec(command, (err, stout, sterr) => {
      if(err) {
        reject(sterr)
      } else {
        resolve(stout)
      }
    })
  })

Use spawn instead (keeping most of your code)

const execWithPromise = command => 
  new Promise((resolve, reject) => {
      const process = childProcess.spawn(command);
      let data = '';
      let error = '';
      process.stdout.on('data', stdout => {
        data += stdout.toString();
      });
      process.stderr.on('data', stderr => {
        error += stderr.toString();
      });
      process.on('error', err => {
        reject(err);
      })
      process.on('close', code => {
        if (code !== 0) {
          reject(error)
        } else {
          resolve(data)
        }
        process.stdin.end();
      });
  });

Use execSync

const execWithPromise = command => childProcess.execSync(command).toString();

Upvotes: 13

Related Questions