175

I'm using the Bluebird promise library under Node.js, it's great! But I have a question:

If you take a look at the documentation of Node's child_process.exec and child_process.execFile you can see that both of these functions are returning a ChildProcess object.

So what's the recommended way to promisify such functions?

Note that the following works (I get a Promise object):

var Promise = require('bluebird');
var execAsync = Promise.promisify(require('child_process').exec);
var execFileAsync = Promise.promisify(require('child_process').execFile);

But how can one get access to the original return value of the original Node.js functions? (In these cases I would need to be able to access the originally returned ChildProcess objects.)

Any suggestion would be appreciated!

EDIT:

Here is an example code which is using the return value of the child_process.exec function:

var exec = require('child_process').exec;
var child = exec('node ./commands/server.js');
child.stdout.on('data', function(data) {
    console.log('stdout: ' + data);
});
child.stderr.on('data', function(data) {
    console.log('stderr: ' + data);
});
child.on('close', function(code) {
    console.log('closing code: ' + code);
});

But if I would use the promisified version of the exec function ( execAsync from above ) then the return value will be a promise, not a ChildProcess object. This is the real problem I am talking about.

peterh
  • 11,875
  • 18
  • 85
  • 108
Zoltan
  • 2,631
  • 2
  • 17
  • 13
  • Do you need both the promise and the `ChildProcess` instance? A code example of how you would like to use the desired function would be helpful. – Bergi Jun 10 '15 at 22:30
  • @Bergi Yes, exactly! I would have needed both the promise and the child process object. It's more like a theoretical question really, because I solved my problem. But here is what I wanted to do: I wanted to execute a program with child_process.execFile and then I wanted to feed (pipe) data into its stdin and read its stdout. And I needed a promise, because of promise chaining. Anyhow, I worked around it by promisifying child_process.exec instead of execFile and run the program via a shell like this: `prg output`. But now I must shell escape everything (both on Windows and *nix) ... – Zoltan Jun 10 '15 at 23:34
  • If you just want to access stdout/err, you don't need the returned object. Because stdout/err are parameters to the callback function. – KFL Apr 19 '16 at 19:23
  • 3
    Check out https://www.npmjs.com/package/child-process-promise and it's associated code (https://github.com/patrick-steele-idem/child-process-promise). – Jason C Dec 12 '16 at 15:56
  • Or you might like the API of https://github.com/jcoreio/promisify-child-process, which allows you to simply `const {stdout, stderr} = await exec('echo test')` – Andy Dec 06 '17 at 21:37

9 Answers9

338

I would recommend using standard JS promises built into the language over an additional library dependency like Bluebird.

If you're using Node 10+, the Node.js docs recommend using util.promisify which returns a Promise<{ stdout, stderr }> object. See an example below:

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

async function lsExample() {
  try {
    const { stdout, stderr } = await exec('ls');
    console.log('stdout:', stdout);
    console.log('stderr:', stderr);
  } catch (e) {
    console.error(e); // should contain code (exit code) and signal (that caused the termination).
  }
}
lsExample()

Handle errors first from stderr.

Eyal
  • 758
  • 6
  • 23
shmck
  • 5,129
  • 4
  • 17
  • 29
85

It sounds like you'd like to return two things from the call:

  • the ChildProcess
  • a promise that resolves when the ChildProcess completes

So "the recommended way to promisify such functions"? Don't.

You're outside the convention. Promise returning functions are expected to return a promise, and that's it. You could return an object with two members (the ChildProcess & the promise), but that'll just confuse people.

I'd suggest calling the unpromisified function, and creating a promise based off the returned childProcess. (Maybe wrap that into a helper function)

This way, it's quite explicit for the next person who reads the code.

Something like:

var Promise = require('bluebird');
var exec = require('child_process').execFile;

function promiseFromChildProcess(child) {
    return new Promise(function (resolve, reject) {
        child.addListener("error", reject);
        child.addListener("exit", resolve);
    });
}

var child = exec('ls');

promiseFromChildProcess(child).then(function (result) {
    console.log('promise complete: ' + result);
}, function (err) {
    console.log('promise rejected: ' + err);
});

child.stdout.on('data', function (data) {
    console.log('stdout: ' + data);
});
child.stderr.on('data', function (data) {
    console.log('stderr: ' + data);
});
child.on('close', function (code) {
    console.log('closing code: ' + code);
});

If you're just wanting to promisify specifically child_process.exec() and child_process.execFile(), in recent node versions there is a better answer here.

Ivan Hamilton
  • 3,205
  • 1
  • 23
  • 14
  • 5
    Thanks a lot for this, got me started on the right track! I needed to tweak it slightly so that exit codes are properly handled: `child.addListener('exit', (code, signal) => { if (code === 0) { resolve(); } else { reject(); } });` – dain Oct 23 '15 at 16:19
  • 2
    In the `child.stderr.on` callback, logging `stderr` instead of `stdout` would be clearer. – GreenRaccoon23 Feb 19 '16 at 21:50
  • +1 for this question/answer; inspired by this answer, I found [`parallel-mochas.js`](https://github.com/elricdesigns/virtualbox/blob/080e19f27d0760839f44623089cd4ec2f600b015/ustwo-example/src/src/app/lib/parallel-mochas.js) which is a similar example using ES6 promises and not the bluebird dependency. – mhulse Sep 29 '16 at 03:56
  • wouldn't it be better here to use `.catch(err) instead of using the reject handler in `.then()`? – Miladinho Feb 11 '19 at 18:52
53

Since Node v12 the built-in util.promisify allows access to the ChildProcess object in the returned Promise for built-in functions where it would have been returned by the un-promisified call. From the docs:

The returned ChildProcess instance is attached to the Promise as a child property.

This correctly and simply satisfies the need to access ChildProcess in the original question and makes other answers out of date providing that Node v12+ can be used.

Adapting the example (and concise style) provided by the questioner, access to the ChildProcess can be achieved like:

const util = require('util');
const exec = util.promisify(require('child_process').exec);
const promise = exec('node ./commands/server.js');
const child = promise.child; 

child.stdout.on('data', function(data) {
    console.log('stdout: ' + data);
});
child.stderr.on('data', function(data) {
    console.log('stderr: ' + data);
});
child.on('close', function(code) {
    console.log('closing code: ' + code);
});

// i.e. can then await for promisified exec call to complete
const { stdout, stderr } = await promise;
user1823021
  • 1,130
  • 10
  • 7
  • 1
    Nice! Looks like introduced in [v12.6.0 ](https://github.com/nodejs/node/pull/28508) – Ivan Hamilton Feb 11 '22 at 19:02
  • lots of googling to find this example. Thank you! Obviously this is doable with manual Promise construction but this solution is a lot more readable. – maschwenk Jan 20 '23 at 16:35
  • command 'java -version' breaks this code. `const promise = exec('java -version');` – Dawit Mar 06 '23 at 18:00
35

Here's another way:

function execPromise(command) {
    return new Promise(function(resolve, reject) {
        exec(command, (error, stdout, stderr) => {
            if (error) {
                reject(error);
                return;
            }

            resolve(stdout.trim());
        });
    });
}

Use the function:

execPromise(command).then(function(result) {
    console.log(result);
}).catch(function(e) {
    console.error(e.message);
});

Or with async/await:

try {
    var result = await execPromise(command);
} catch (e) {
    console.error(e.message);
}
Manohar Reddy Poreddy
  • 25,399
  • 9
  • 157
  • 140
LachoTomov
  • 3,312
  • 30
  • 42
  • 1
    This is not suitable if you want to stream stdout or stderr, for instance when they are very sizeable. – Jeroen Apr 08 '18 at 14:40
  • 2
    You can also use `util.promisify` and then access `.stdout`. – Lucas May 07 '18 at 21:08
  • 2
    @Lucas You should post it as answer. `const execAsync = require('util').promisify(require('child_process').exec);` – MrHIDEn Jan 18 '19 at 15:44
9

There's probably not a way to do nicely that covers all use cases. But for limited cases, you can do something like this:

/**
 * Promisified child_process.exec
 *
 * @param cmd
 * @param opts See child_process.exec node docs
 * @param {stream.Writable} opts.stdout If defined, child process stdout will be piped to it.
 * @param {stream.Writable} opts.stderr If defined, child process stderr will be piped to it.
 *
 * @returns {Promise<{ stdout: string, stderr: stderr }>}
 */
function execp(cmd, opts) {
    opts || (opts = {});
    return new Promise((resolve, reject) => {
        const child = exec(cmd, opts,
            (err, stdout, stderr) => err ? reject(err) : resolve({
                stdout: stdout,
                stderr: stderr
            }));

        if (opts.stdout) {
            child.stdout.pipe(opts.stdout);
        }
        if (opts.stderr) {
            child.stderr.pipe(opts.stderr);
        }
    });
}

This accepts opts.stdout and opts.stderr arguments, so that stdio can be captured from the child process.

For example:

execp('ls ./', {
    stdout: new stream.Writable({
        write: (chunk, enc, next) => {
            console.log(chunk.toString(enc));
            next();
        }
    }),
    stderr: new stream.Writable({
        write: (chunk, enc, next) => {
            console.error(chunk.toString(enc));
            next();
        }
    })
}).then(() => console.log('done!'));

Or simply:

execp('ls ./', {
    stdout: process.stdout,
    stderr: process.stderr
}).then(() => console.log('done!'));
edan
  • 1,119
  • 1
  • 14
  • 13
5

Just want to mention that there's a nice tool that will solve your problem completely:

https://www.npmjs.com/package/core-worker

This package makes it a lot easier to handle processes.

import { process } from "CoreWorker";
import fs from "fs";

const result = await process("node Server.js", "Server is ready.").ready(1000);
const result = await process("cp path/to/file /newLocation/newFile").death();

or combine these functions:

import { process } from "core-worker";

const simpleChat = process("node chat.js", "Chat ready");

setTimeout(() => simpleChat.kill(), 360000); // wait an hour and close the chat

simpleChat.ready(500)
    .then(console.log.bind(console, "You are now able to send messages."))
    .then(::simpleChat.death)
    .then(console.log.bind(console, "Chat closed"))
    .catch(() => /* handle err */);
Tobias
  • 956
  • 8
  • 16
  • 11
    Based on this recommendation, I went off and used core-worker. I found it to be extremely opaque with regards to providing output and found that it would throw non-zero exit codes when commands completed properly. I would not use that library. – pbanka Mar 18 '16 at 15:38
  • May you be so kind and open an issue / issues if you're having trouble with the lib? We're using it everywhere where it comes to external processes and it's a core lib of our functional test suite used in dozens of productive projects. – Tobias Jan 21 '18 at 22:03
3

Just another example you might run into issues when running multiple commands when destructuring with the same const's you can rename them like this.

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

async function runCommands() {
    try {
        const { stdout, stderr } = await exec('ls');
        console.log('stdout:', stdout);
        console.log('stderr:', stderr);

        const { stdout: stdoutTwo, stderr: stderrTwo } = await exec('ls');
        console.log('stdoutTwo:', stdoutTwo);
        console.log('stderrTwo:', stderrTwo);

        const { stdout: stdoutThree, stderr: stderrThree } = await exec('ls');
        console.log('stdoutThree:', stdoutThree);
        console.log('stderrThree:', stderrThree);

    } catch (e) {
        console.error(e); // should contain code (exit code) and signal (that caused the termination).
    }
}
runCommands()
user1503606
  • 3,872
  • 13
  • 44
  • 78
2

Here are my two cents. Uses spawn which streams the output and writes to stdout and stderr. The error and standard output is captured in buffers and are returned or rejected.

This is written I Typescript, feel free to remove typings if using JavaScript:

import { spawn, SpawnOptionsWithoutStdio } from 'child_process'

const spawnAsync = async (
  command: string,
  options?: SpawnOptionsWithoutStdio
) =>
  new Promise<Buffer>((resolve, reject) => {
    const [spawnCommand, ...args] = command.split(/\s+/);
    const spawnProcess = spawn(spawnCommand, args, options);
    const chunks: Buffer[] = [];
    const errorChunks: Buffer[] = [];
    spawnProcess.stdout.on("data", (data) => {
      process.stdout.write(data.toString());
      chunks.push(data);
    });
    spawnProcess.stderr.on("data", (data) => {
      process.stderr.write(data.toString());
      errorChunks.push(data);
    });
    spawnProcess.on("error", (error) => {
      reject(error);
    });
    spawnProcess.on("close", (code) => {
      if (code === 1) {
        reject(Buffer.concat(errorChunks).toString());
        return;
      }
      resolve(Buffer.concat(chunks));
    });
  });
Jonas D.
  • 361
  • 4
  • 18
Richard
  • 14,427
  • 9
  • 57
  • 85
0

Here's mine. It doesn't deal with stdin or stdout, so if you need those then use one of the other answers on this page. :)

// promisify `child_process`
// This is a very nice trick :-)
this.promiseFromChildProcess = function (child) {
    return new Promise((resolve, reject) => {
        child.addListener('error', (code, signal) => {
            console.log('ChildProcess error', code, signal);
            reject(code);
        });
        child.addListener('exit', (code, signal) => {
            if (code === 0) {
                resolve(code);
            } else {
                console.log('ChildProcess error', code, signal);
                reject(code);
            }
        });
    });
};
Jay
  • 173
  • 1
  • 7
  • Welp, maybe this is not the first option you want to try to solve yours problems with 'child_process' but I can confirm this actually works. – asceta Dec 19 '20 at 02:16