node.jsexecchild-process

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?


Solution

  • 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();