Skip to content

Instantly share code, notes, and snippets.

@Stuk
Created August 14, 2013 00:15
Show Gist options
  • Save Stuk/6226938 to your computer and use it in GitHub Desktop.
Save Stuk/6226938 to your computer and use it in GitHub Desktop.
Wrap Node's `child_process.spawn` with a promise interface that rejects if the process has an error, or exits with a code other than zero.
var spawn = require("child_process").spawn;
var Q = require("q");
/**
* Wrap executing a command in a promise
* @param {string} command command to execute
* @param {Array<string>} args Arguments to the command.
* @param {string} cwd The working directory to run the command in.
* @return {Promise} A promise for the completion of the command.
*/
module.exports = function exec(command, args, cwd) {
if (!command || !cwd) {
return Q.reject(new Error("Both command and working directory must be given, not " + command + " and " + cwd));
}
if (args && !args.every(function (arg) {
var type = typeof arg;
return type === "boolean" || type === "string" || type === "number";
})) {
return Q.reject(new Error("All arguments must be a boolean, string or number"));
}
var deferred = Q.defer();
console.log("+", command, args.join(" "), "# in", cwd);
var proc = spawn(command, args, {
cwd: cwd,
stdio: global.DEBUG ? "inherit" : "ignore"
});
proc.on("error", function (error) {
deferred.reject(new Error(command + " " + args.join(" ") + " in " + cwd + " encountered error " + error.message));
});
proc.on("exit", function(code) {
if (code !== 0) {
deferred.reject(new Error(command + " " + args.join(" ") + " in " + cwd + " exited with code " + code));
} else {
deferred.resolve();
}
});
return deferred.promise;
};
@sukima
Copy link

sukima commented Feb 5, 2014

Thanks this was a perfect example. I going to use it in my codez. 👍

@horstmeier
Copy link

Realy nice, helped me a lot.

@sukima
Copy link

sukima commented Jun 30, 2015

I found that the errors were harder to mange. Especially since most programs print thier errors to stderr. (That isn't even considering the misbehaved programs that print errors to stdout!) The only way to manage it is to buffer the results. I gathered the following from child-process-promised:

var spawn = require('child_process').spawn;

function spawnAsPromised() {
  var args = Array.prototype.slice.call(arguments);
  return new Promise(function(resolve, reject) {
    var stdout = '', stderr = '';
    var cp = spawn.apply(null, args);
    cp.stdout.on('data', function(chunk) {
      stdout += chunk;
    });
    cp.stderr.on('data', function(chunk) {
      stderr += chunk;
    });
    cp.on('error', reject)
      .on('close', function(code) {
        if (code === 0) {
          resolve(stdout);
        } else {
          reject(stderr);
        }
      });
  });
}

I expanded on this for dealing with really large streams of JSON by means of JSONStream:

var spawn = require('child_process').spawn;
var JSONStream = require('JSONStream');

function spawnStreamJSON() {
  var args = Array.prototype.slice.call(arguments);
  return new Promise(function(resolve, reject) {
    var stderr = '';
    var cp = spawn.apply(null, args);
    cp.stderr.on('data', function(chunk) {
      stderr += chunk;
    });
    cp.on('error', reject)
      .on('close', function(code) {
        if (code !== 0) {
          reject(stderr);
        }
      });
    cp.stdout.pipe(JSONStream.parse())
      .on('root', resolve)
      .on('error', reject);
  });
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment