3

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?

Michael
  • 6,823
  • 11
  • 54
  • 84

2 Answers2

14

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();
Peter Grainger
  • 4,539
  • 1
  • 18
  • 22
  • is it necessary for the callback to be an `async` function? Is not calling `reject` due to the inconsistent way command line execution can return to either stout or sterr depending on the program being executed? Thanks for these examples! – David Jul 18 '22 at 12:15
  • The function passed to a promise is not a callback. It's a function that is executed *synchronously* when the promise is created, and *exposes* the two `resolve` and `reject` callbacks. The promise would be pointless, as the `new Promise` call wouldn't return until the operation is complete. `execSync` will return successfully if the command returns a 0 exit code, otherwise throws the error. If the command being run writes to stderr and then exits with 0, then you would need to use the spawn method and figure out what to do based on the text (arguably, that would be a bug in the command). – Eric Haynes Aug 15 '22 at 00:29
  • @EricHaynes thanks for bringing this to my attention. I've updated for node 16 and fully tested each solution. I'm not sure if they ever were correct! – Peter Grainger Aug 16 '22 at 04:06
2

I know this is an old question but here is a useful tool I discovered with node a while back...So, say you have a node file app.ts, in typescript that is...

app.ts

import utils from 'util'; // The thing that is useful, it has a bunch of useful functions
import { exec } from 'child_process'; // The exec import

export const execute = utils.promisify(exec);

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

run();

In js though it would probably something like this

app.js

const utils = require('util');
const exec = require('child_process').exec;

const execute = utils.promisify(exec);

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

run();
arve0
  • 3,424
  • 26
  • 33
bloo
  • 1,416
  • 2
  • 13
  • 19