Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node Grunt Asynchronous Task in Loop with Closure not Working

Thanks in advance for taking a look at this.

I've got an async task within a loop that's not working. I've made sure to:

  1. Wrap my looping variable "key" in a closure to avoid the classic "last value only" problem, where the loop finishes long before the async tasks returns a value, and only the last value is displayed.
  2. Call done(error) to complete my async task (per the Gruntjs FAQ)
  3. Use hasOwnProperty() to make sure that the key is an actual property of an object, and doesn't come from the prototype.
  4. Follow the format of the node.exec example, with the exception of assigning the value of exec to a variable--which I've tried, but it didn't help. See first reference below.

This function outputs... nothing!? For some reason the grunt.log.writeln statements aren't even firing. Task completes without errors. I also tried adding a 20-second delay, in case the script was finishing before the async task were returned. Oddly, if I don't call "done(error)," the files are written to file (when I replace the writeln's with grunt.file.write statements).

var done = this.async(),
    exec = require('child_process').exec,
    myJSON = {
      "file1" : "C:/home/me/jquery.js",
      "file2 " : "C:/home/me/grunt.js",
      ...
      "fileN" : "C:/home/me/N.js"
    },
    count;

for (var key in myJSON) {
    if (myJSON.hasOwnProperty(key)) {
      (function (key) { 
        exec( 'type "' + myJSON[key] + '"', 
          function(error, stdout, stderr) {
            if (error) {
                grunt.log.writeln('!!! exec error: ' + error);
            }
            grunt.log.writeln('stdout: ' + stdout);
            grunt.log.writeln('stderr: ' + stderr);
            done(error);
          }
        );
      })(key);
    count++;
  }
}

References:

http://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback https://github.com/gruntjs/grunt/wiki/Frequently-Asked-Questions

like image 523
user1354017 Avatar asked Jan 18 '13 21:01

user1354017


1 Answers

done() should only be called when the whole operation is complete, which means when all the exec() methods has run their callbacks. Currently you're executing the done callback on each iteration. You can easily achieve this by using the forEach method in the node module async which comes with grunt (grunt.util.async(, or forEachSeries if you want the exec() methods executed in order.

Something like this (not tested):

var done = this.async();
var exec = require('child_process').exec;
var async = grunt.util.async; // updated
var myJSON = {
  "file1": "C:/home/me/jquery.js",
  "file2": "C:/home/me/grunt.js",
  ...
  "fileN": "C:/home/me/N.js"
};

async.forEach(Object.keys(myJSON), function(el, cb) {
  exec('type "' + myJSON[el] + '"', function(error, stdout, stderr) {
      if (error) {
          grunt.warn('!!! exec error: ' + error)
          return cb(error);
      }
      grunt.log.writeln('stdout: ' + stdout);
      grunt.log.writeln('stderr: ' + stderr);
      cb();
    }
  );
}, function(error) {
  done(!error);
});
like image 116
Sindre Sorhus Avatar answered Oct 20 '22 02:10

Sindre Sorhus