5

I have a list of shell commands I want to execute with nodejs:

// index.js
var commands = ["npm install", "echo 'hello'"];

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

for (var i = 0; i < commands.length; i++) {
    exec(commands[i], function(err, stdout) {
        console.log(stdout);
    });
}

When I run this, the commands are executed in the reverse order. Why is this happening? How do i execute the commands in sequence?

Better yet, is there a way to execute shell commands without using nodejs? I find its async handling of the shell a little cumbersome.

NOTE:

I know that libraries like shelljs exist. I'm trying to do this with base nodejs only.

3
  • It's more likely the echo command executes faster since exec is an async method. This is a hint: you need to rethink how you're setting up the execution. Commented Apr 30, 2016 at 19:53
  • they are not executed in reverse order, they are asynchronous and one is not waiting for the other. Shell commands are executed by shell, while exec is a mere interface to access it. You can run any of this commands in your terminal Commented Apr 30, 2016 at 19:54
  • Asynchronous for cycle in JavaScript Commented Apr 30, 2016 at 20:20

2 Answers 2

14

Your for loop is executing all your asynchronous operation in parallel at once because exec() is non-blocking. The order they will complete depends upon their execution time and will not be determinate. If you truly want them to be sequenced, then you have to execute one, wait for it to call it's completion callback and then execute the next one.

You can't use a traditional for loop to "wait" on an asynchronous operation to complete in Javascript in order to execute them sequentially. Instead, you have to make the iteration manually where you kick off the next iteration in the completion callback of the previous one. My usual way of doing that is with a counter and a local function called next() like this:

Manual Async Iteration

var commands = ["npm install", "echo 'hello'"];

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

function runCommands(array, callback) {

    var index = 0;
    var results = [];

    function next() {
       if (index < array.length) {
           exec(array[index++], function(err, stdout) {
               if (err) return callback(err);
               // do the next iteration
               results.push(stdout);
               next();
           });
       } else {
           // all done here
           callback(null, results);
       }
    }
    // start the first iteration
    next();
}

runCommands(commands, function(err, results) {
    // error or results here
});

ES6 Promises

Since promises have been standardized in ES6 and are built into node.js now, I like to use Promises for my async operations:

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

function execPromise = function(cmd) {
    return new Promise(function(resolve, reject) {
        exec(cmd, function(err, stdout) {
            if (err) return reject(err);
            resolve(stdout);
        });
    });
}

var commands = ["npm install", "echo 'hello'"];

commands.reduce(function(p, cmd) {
    return p.then(function(results) {
        return execPromise(cmd).then(function(stdout) {
            results.push(stdout);
            return results;
        });
    });
}, Promise.resolve([])).then(function(results) {
    // all done here, all results in the results array
}, function(err) {
    // error here
});

Bluebird Promises

Using the Bluebird promise library, this would be even simpler:

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

var commands = ["npm install", "echo 'hello'"];
Promise.mapSeries(commands, execP).then(function(results) {
    // all results here
}, function(err) {
    // error here
});
Sign up to request clarification or add additional context in comments.

Comments

2

Opt.1: Use the '...Sync' version of the function if it exists

In this case there is already an execSync function:

child_process.execSync(command[, options])

Opt.2: Generators magic!

For a more general purpose, nowadays you could use e.g. this 'generator' pattern to 'deasync' any async function inside them, very useful for any sequential OS script.

Here an example of how to use readline async function in a sync fashion in node.js v6+ (I think also v4+)

var main = (function* () {
  var rl = require('readline')
          .createInterface({input: process.stdin, output: process.stdout });
  // the callback uses the iterator '.next()' to resume the 'yield'
  a = yield rl.question('do you want this? ', r=>main.next(r))  
  b = yield rl.question('are you sure? ', r=>main.next(r))      
  rl.close()
  console.log(a,b)
})()          // <- generator executed, iterator 'main' created
main.next()   // <- start iterator, run till the first 'yield'

1 Comment

Some examples would be nice!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.