Search code examples
javascriptnode.jscallbackgulp

Async completion error when function called, or callback not a function when called on default function


I'm trying to make a function which calls some other functions :

copy = () => {
    copyHtml();
    copyCss();
    copyJs();
    copyImg();
}
exports.copy = copy;

Using gulp copy, the function works but I get this error :

The following tasks did not complete: copy. Did you forget to signal async completion?

I'm not used to it and after searching I just changed my function like below, it works without error :

copy = (done) => {
    copyHtml();
    copyCss();
    copyJs();
    copyImg();
    done();
}
exports.copy = copy;

Then I added it to my default function :

defaultFunction = () => {
    copy();
    browsersyncServe();
}
exports.default = defaultFunction;

My problem is when I call the default function with gulp :

done is not a function

If I directly call copyHtml, copyCss, copyJs and copyImg in the default function, it works and I get no error.

What am I missing ?


Solution

  • It appears that your functions run synchronously, without returning anything. While this works in early versions of gulp, currently it no longer supports synchronous functions. Now you need to use a callback (like in your question), or return a stream, promise, event emitter, child process, or observable (see https://stackoverflow.com/a/36899424 for some implementations).

    For your case, there are a bunch of things that could work, but here are three possibilities:

    Using async Functions

    Because gulp supports promises, you can just make all of your functions async. You can call these functions like synchronous functions (since they are really synchronous under the hood):

    copy = async () => {
        copyHtml();
        copyCss();
        copyJs();
        copyImg();
    }
    defaultFunction = async () => {
        // the await isn't strictly necessary, since copy is actually
        // synchronous, but it's probably good practice to use await,
        // if copy ever becomes asynchronous
        await copy(); 
        browsersyncServe();
    }
    exports.copy = copy;
    exports.default = defaultFunction;
    

    Returning a Promise

    In the same vein, you can return a resolved promise (like Promise.resolve()) for any synchronous function gulp uses. Like the above example, this doesn't interfere with the synchronous invocation of these functions (as long as the returned value is ignored):

    copy = () => {
        copyHtml();
        copyCss();
        copyJs();
        copyImg();
        return Promise.resolve();
    }
    defaultFunction = () => {
        copy();
        browsersyncServe();
        return Promise.resolve();
    }
    exports.copy = copy;
    exports.default = defaultFunction;
    

    Wrapping callback functions

    Another possibility is to wrap your synchronous functions to become asynchronous. For example, using the asynchronous callback format would look something like this:

    function wrapSyncToAsync(fn) {
        return function (done) {
            fn();
            done();
        };
    }
    copy = () => {
        copyHtml();
        copyCss();
        copyJs();
        copyImg();
    }
    defaultFunction = () => {
        copy();
        browsersyncServe();
    }
    exports.copy = wrapSyncToAsync(copy);
    exports.default = wrapSyncToAsync(defaultFunction);
    

    Here copy is your synchronous version that you can call via just copy(), while exports.copy is what gulp will call with a callback, e.g. exports.copy(callbackFunction).

    You can use wrapSyncToAsync to convert synchronous functions (that don't have any arguments) to asynchronous functions.