Lukas
Lukas

Reputation: 7734

Async.js with Gulp turns error "Callback was already called"

I'm using async.js to load my gulp tasks in some ordered sequence, so i do this:

// Gulp Default Build Task
gulp.task('default', function() {
    var tasks = ['sass_dev', 'browserify', 'lint', 'watch'];
    var sync = tasks.map(function(task) {
        return function(callback) {
            gulp.start(task, function(err) {
                if (err || task.length === 0) {
                    callback(err);
                } else {
                    callback(null, task[0]);
                }
            });
        };
    });
    async.series(sync);
});

Tasks running correctly but after first change in scripts i have an error which is if (fn === null) throw new Error("Callback was already called.");. I know that is bc it's calling callback twice but even with err condition it's not working. Can anybody help?

Upvotes: 0

Views: 591

Answers (3)

Louis
Louis

Reputation: 151401

The Problem

The reason you've run into this issue is that you are using a part of Gulp which is really not made to be used by end-users of Gulp. Here is a little gulpfile that reproduces the behavior you've reported:

var gulp = require("gulp");

gulp.task('foo');

gulp.task('default', function () {
    gulp.start('foo', function () {
        console.log("callback!");
    });
    setTimeout(function () {
        gulp.start('foo');
    }, 1000);
});

Running this will produce on the console:

[12:23:48] Using gulpfile /tmp/t15/test.js
[12:23:48] Starting 'default'...
[12:23:48] Starting 'foo'...
[12:23:48] Finished 'foo' after 66 μs
[12:23:48] Finished 'default' after 1.24 ms
callback!
[12:23:49] Starting 'foo'...
[12:23:49] Finished 'foo' after 8.99 μs
callback!

You see that callback! shows up twice, even though gulp.start is called with it only once with a callback! The problem is that Gulp remembers the callback and uses it even for the 2nd call of gulp.start.

If you want the gory details, look at orchestrator, which is what Gulp uses. Look for doneCallback and how it is managed. The basic behavior is if start is called with a callback, then it obliterates any previous value of doneCallback but if it is not called with a callback, then the old value remains the same and is in effect reused by subsequent calls to start.

A safe solution

Anyhow. It appears you want to run a bunch of tasks in sequence. The way I've done this in the past is to declare the task that will run other tasks in sequence so that:

  1. Its dependencies is the union of all dependencies of the tasks to be run in sequence.

  2. Its implementation function calls the implementation function of all the tasks it should run in sequence.

Here is a brief example:

var gulp = require("gulp");

gulp.task('x');
gulp.task('y');
gulp.task('q');

// We are defining task `a`, with dependencies `x` and `y`.
var a_deps = ['x', 'y'];
function a() {
    console.log("a");
}
gulp.task('a', a_deps);

// We are defining task `b`, with dependencies `x` and `q`.
var b_deps = ['x', 'q'];
function b() {
    console.log("b");
}
gulp.task('b', b_deps);

// We are defining task `default`, which is equivalent to running tasks
// `a` and `b` in sequence.
gulp.task('default', a_deps.concat(b_deps), function () {
    a();
    b();
});

For the record, I do not use run-sequence because when I tried it, I saw it running my dependencies multiple times, which was not acceptable to me.

When Gulp 4 comes out, we'll be able to use gulp.series to sequence tasks

Upvotes: 1

Lukas
Lukas

Reputation: 7734

Actaully i've used other, lighter library called run-sequence and it's work nice and smooth. So this is the example solution:

var sequence    = require('run-sequence');

gulp.task('default', function(callback) {
    sequence('sass_dev', ['browserify', 'lint'], 'watch', callback);
});
gulp.task('prod', function(callback) {
    sequence('sass_dev', ['browserify'], 'uglify', callback);
});

npm link

Upvotes: 0

J. Mark Stevens
J. Mark Stevens

Reputation: 4945

There is a simpler way.

'use strict';

let gulp = require('gulp');
let concat = require('gulp-concat');
let minifyCSS = require('gulp-minify-css');
let browserify = require('browserify');
let vsource = require("vinyl-source-stream");
let babel = require('babelify');

let source = {
	appjs: './ui-src/app.js',
	js: ['./ui-src/**/*.js'],
	libjs: ['./ui-src/lib/primus/primus.js'],
	appcss: ['./ui-src/css/*.css'],
	apphtml: ['./ui-src/**/*.html'],
	appimg: ['./ui-src/img/*']
};

gulp.task('appjs', function(){
	browserify({ debug: true })
		.transform(babel.configure({stage: 0}))
		.require(source.appjs, { entry: true })
		.bundle()
		.pipe(vsource('app.min.js'))
		.pipe(gulp.dest('./ui-dist'));
});

gulp.task('libjs', function () {
	gulp.src(source.libjs)
		.pipe(concat('lib.min.js'))
		.pipe(gulp.dest('./ui-dist'))
});

gulp.task('appcss', function () {
	gulp.src(source.appcss)
		.pipe(concat('app.min.css'))
		.pipe(minifyCSS())
		.pipe(gulp.dest('./ui-dist'))
});

gulp.task('apphtml', function() {
	gulp.src(source.apphtml)
		.pipe(gulp.dest('./ui-dist'));
	gulp.src(source.appimg, {base: 'ui-src'})
		.pipe(gulp.dest('./ui-dist'));
});

gulp.task('watch', function() {
	gulp.watch(source.appcss, ['appcss']);
	gulp.watch(source.apphtml, ['apphtml']);
	gulp.watch(source.js, ['appjs']);
});

gulp.task('default', ['appjs', 'appcss', 'apphtml', 'watch']);

gulp.task('nw', ['appjs', 'libjs', 'appcss', 'apphtml']);

Upvotes: 0

Related Questions