igorsantos07
igorsantos07

Reputation: 4686

How to make Grunt wait for a task to finish before running another?

Here's my Gruntfile and the output.

As you can see in the output, there are a couple of issues related to asynchronous tasks:

  1. imagemin is called and the next one comes straight ahead. This makes its output appear in the end of the tasks, what's quite messy;
  2. build, which is a custom task, is using var done = this.async() and calling done() after finishing the command; however, this only works correctly if I run the task alone; running it with another tasks makes it run async too;
  3. With build running later, jasmine has nothing to test and thus is useless.

Is there a way to fix this behavior?

Upvotes: 11

Views: 10099

Answers (2)

Carlos Morales
Carlos Morales

Reputation: 5916

As you could read in the Grunt documentation:

If a task is asynchronous, this.async method must be invoked to instruct Grunt to wait. It returns a handle to a "done" function that should be called when the task has completed.

And a short example would be similar to:

// Tell Grunt this task is asynchronous.
var done = this.async();

// Your async code.
fetchData(url).then( data => {
    console.log(data);
    done();
}).catch( error => {
    console.err(error);
    done(false); // false instructs Grunt that the task has failed
});

Upvotes: 0

dc5
dc5

Reputation: 12441

I believe your problem is with this task:

grunt.registerTask('prepare-dist', 'Creates folders needed for distribution', function() {
            var folders = ['dist/css/images', 'dist/imgs/icons'];
            for (var i in folders) {
                    var done = this.async();
                    grunt.util.spawn({ cmd: 'mkdir', args: ['-p', folders[i]] }, function(e, result) {
                            grunt.log.writeln('Folder created');
                            done();
                    });
            }
    });

If you have multiple folders, both async() and done() will be called multiple times. Async is implemented as a simple flag (true/false) and is meant to be called once. The first done() call allows any follow on tasks to run.

There are many ways to move the calls to async and done out of the loop. A quick google search on something like: nodejs how to callback when a series of async tasks are complete will give you some additional options. A couple of quick (& dirty) examples:

// Using a stack
(function() {
    var work = ['1','2','3','4','5']


    function loop(job) {
        // Do some work here
        setTimeout(function() {
            console.log("work done");

            work.length ? loop(work.shift()) : done();
        }, 500);
    }

    loop(work.shift());

    function done() {
        console.log('all done');
    }
})();

-- or --

// Using a counter (in an object reference)
(function() {
    var counter = { num: 5 }

    function loop() {
        // Do some work here
        setTimeout(function() {
            --counter.num;

            console.log("work done");

            counter.num ? loop() : done();
        }, 500);
    }

    loop();

    function done() {
        console.log('all done');
    }
})();

Upvotes: 9

Related Questions