3

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 ?

3 Answers 3

4
+50

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.

2

The copy function needs a callback in its parameter, but you call it without parameter in defaultFunction, which will be undefined. That's why it complains "done is not a function".

Looks like all your functions are synchronized, done is no use. So you can just pass a dummy function to copy:

// the defaultFunction should call the done callback on finish as well
defaultFunction = (done) => {
    copy(() => {});
    browsersyncServe();
    done();
}
exports.default = defaultFunction;

BTW, a better way to perform actions one by one in the gulp task is to use the series to compose the functions, and each of the function to be composed need to accept a callback function:

const { series } = require('gulp');

function copyHtml(done) {
  // .....
  done();  // callback when it is done
}

copy = series(
    copyHtml,
    copyCss,
    copyJs,
    copyImg
);

exports.copy = copy;
exports.default = series(copy, browsersyncServe);
1
  • Instead of passing a dummy function, shouldn't you use copy(() => { browsersyncServe(); done(); })? Maybe even with nodeback-style error propagation?
    – Bergi
    Commented Oct 21, 2022 at 9:16
0

When you call this :

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

you are calling copy() without any arguments. Hence here:

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

done is not defined right. That is the error.

You can pass done:

defaultFunction = (done) => {
    copy(done);
    browsersyncServe();
}
exports.default = defaultFunction;
3
  • If copy needs a done callback because it's asynchronous, then browsersyncServe should not be called immediately after it
    – Bergi
    Commented Oct 21, 2022 at 9:15
  • @Bergi Can you please explain me why it shouldn't ?
    – Cédric
    Commented Oct 24, 2022 at 14:13
  • @Cédric, you're supposed to call done only when the task is completed. Calling it before browsersyncServe starts or is completed might cause unintended behavior.
    – Steve
    Commented Oct 25, 2022 at 17:52

Not the answer you're looking for? Browse other questions tagged or ask your own question.