Grunt Plugin- async.each with globbing pattern

I'm working on a grunt plugin that was written (by someone else) to receive hard-coded file names (src and dest), but I'm trying to change it to be able to be pointed to a directory with a globbing pattern and specify an output folder for the "dest". But I'm having trouble with the async.each, because my initial implementation has a nested async.each. Specifically, I think I have a problem with when to call the callback(). I'm getting hung up in some loop somewhere.

This does work as written because the files are created correctly both ways of configuring the Gruntfile.js, but the previously-working tests are now broken.

I'm just wondering about how to structure the second nested loop. Perhaps that doesn't need to use async?

The Gruntfile.js should be able to be config'd as:

myplugin: {
    dev: {
        files : {
            'src/business.html': 'src/test_src/business.test',
            ...
        }
    }
},

or as a globbing pattern (this is what I'm adding)

myplugin: {
    dev: {
        src: ['src/test_src/*.test'],
        dest: 'output'
    }
},

The plugin started out with a single async.each, with each loop handling a specific "files" src/dest. But when we're using a globbing pattern, there's only one outer loop, the pattern, so I need a second async.each to handle the actual files (there are ~11).

grunt.registerMultiTask('myplugin', 'Compiles files using myplugin', function () {

    done = this.async();

    // Iterate over all specified file groups.
    async.each(this.files, function (fileGlob, cb) {
        var destination = fileGlob.dest;
        grunt.log.debug("FileGlob: " + fileGlob);

        async.each(fileGlob.src, function (filepath, callback) {
            if (notAPartial(filepath) && grunt.file.exists(filepath)) {
                if (filepath.match(/\.(test|html)$/)) {
                    grunt.log.debug('test compilation of ' + filepath);
                    compileMyFile(filepath, destination);
                } else {
                    // this callback is from the orig version
                    // i think it's causing problems with the nested async.each calls
                    callback(new Error("No handler for filetype: " + filepath));
                }
            }
        }, function(err) {
            if(err) done(err);
            else done();
        });
        cb();
    }, function(err) {
        if(err) done(err);
        else done();
        grunt.log.ok("Compiled " + count + " files.");
    });
})

It looks like your callbacks are a little out of place. The signature for async.each is: async.each(arrayOfThings, callbackPerThing, callbackWhenWeGotThroughAllThings).

For nesting async.each statements, I like to name the callbacks based on what they do to avoid confusion when nesting, such as:

var done = this.async();
async.each(this.files, function(fileGlob, nextGlob) {
  async.each(fileGlob.src, function(filepath, nextFile) {
    doSomethingAsync(function() {
      // Continue to the next file
      nextFile();
    });
  }, function() {
    // When we are done with all files in this glob
    // continue to the next glob
    nextGlob();
  });
}, function() {
  // When we are done with all globs
  // call done to tell the Grunt task we are done
  done();
});

In your case above, you are right about not needing the inner async.each. Nor do you need the outer async.each as none of the operations appear to be asynchronous. You can more simply do the following:

grunt.registerMultiTask('myplugin', 'Compiles files using myplugin', function () {
  this.files.forEach(function(fileGlob) {
    var destination = fileGlob.dest;
    grunt.log.debug("FileGlob: " + fileGlob);

    fileGlob.src.forEach(function(filepath) {
      if (notAPartial(filepath) && grunt.file.exists(filepath)) {
        if (filepath.match(/\.(test|html)$/)) {
          grunt.log.debug('test compilation of ' + filepath);
          compileMyFile(filepath, destination);
         } else {
           grunt.log.error("No handler for filetype: " + filepath);
         }
       }
    });
  });

  grunt.log.ok("Compiled " + count + " files.");
});