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.
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
});
In this case there is already an execSync
function:
child_process.execSync(command[, options])
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'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With