Gulp task completion: diagnosing stream return versus done callback in series()
0 reputation · 14 Oct 2021, 13:50 UTC
0 reputation · 14 Oct 2021, 13:50 UTC
In Gulp 4 and later, tasks composed with series() must signal asynchronous completion exactly once, but the API accepts multiple mechanisms: returning a stream or promise, or declaring and invoking a done callback. The unresolved decision is which signal to use per task and whether mid-pipeline errors reliably abort the remaining sequence.
A task that declares no parameters and returns nothing is treated as synchronous, so asynchronous work started inside it can be cut off when the process exits. A task that declares done but never calls it causes Gulp to report that async completion was not signaled. Returning a stream or promise is idiomatic for file pipelines, yet attaching an error handler that only logs can make a failed pipeline report success. In series(), a failing task stops the remaining tasks, but exact fail-fast semantics and error wording vary by gulp/undertaker version, and Gulp 5 introduced different internals and Node requirements.
Should each task return its pipeline stream or promise instead of using the done callback? Is mixing signals—such as returning a stream while also calling done—unsupported and likely to mask or duplicate failures? How should error handlers be attached so that stream errors fail the task rather than being swallowed?
To ensure predictable execution in series(), every task must signal completion exactly once. You should prioritize returning a stream or a promise over using the done callback for file-based pipelines. Mixing these signals—such as returning a stream while also invoking done()—is unsupported and typically triggers a "Task completed multiple times" warning or causes race conditions that mask failures.
Choose one of the following based on the task's nature:
end and error events.done callback.A common failure point in series() is the use of .on('error', ...) handlers that only log the error. Because Gulp relies on the stream's error event to trigger a task failure, swallowing that error makes Gulp believe the task succeeded, allowing the sequence to continue with corrupted or missing files.
To ensure a pipeline failure halts the series(), you must either:
gulp-plumber to manage errors without breaking the watch process, though this requires careful configuration to ensure series() still receives the failure signal.You can verify your task signaling behavior with these scoped tests:
// Test 1: Verify fail-fast behavior
gulp.task('fail-task', () => {
return gulp.src('src/*.js')
.pipe(somePluginThatFails())
.on('error', (err) => {
console.log('Logged error, but if I don't re-emit, series() continues');
// To stop series(), this must be: this.emit('error', err);
});
});
// Test 2: Verify sequence
gulp.task('default', gulp.series('fail-task', 'should-not-run'));
This guidance assumes Gulp 4.0+ using the undertaker registry. While Gulp 5 maintains these core signaling requirements, it introduces stricter Node.js version requirements. If you are using a custom stream implementation that does not emit standard end or error events, you must use the done callback to signal completion manually.
Diagnostic Detail Needed: Are you using a custom stream wrapper or a specific error-handling plugin (like gulp-plumber)? This determines whether the error is being swallowed by the plugin or by your own event listeners.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.