Catching errors on Gulp.js

Watch mode is important thing of modern build systems, especially for front-end. Gulp also has this feature but with a few caveats, which makes work more difficult. This is a stream-based build system, so you have to deal with Steam API, where you must catch every error, otherwise uncaught exception will be thrown and will stop your build process.

This is especially important for watcher, because watch daemon lives for a long time, and that is not good when uncaught error kills it.

What’s going on?

First of all, we need to figure out, why it is happening, why watcher sometimes fails and dies. The recommended way to notify about occurred errors in gulp plugins is an emitting `error` event. But node.js streams, which are actually utilized in gulp, doesn’t allow to keep errors unnoticed. If there is no listeners on `error` event on some stream, in case of error an unhandled exception will be thrown. That needed to be sure that error message from stream reached its users. As the result, while working with gulp, developers often see following error in console:

events.js:72 
throw er; // Unhandled ‘error’ event

While you are running build on Continuous-Integration sever, this behaviors can be useful, it helps to catch bugs. But this errors on local development with watcher which may be annoying, because watcher always need to be restarted.

What to do?

There is many discussions about that issue, for example on stackoverflow. Also there is an issue on Github in the gulp repo. There are proposed some solutions.

First, subscribe on error event and don’t allow to throw unhandled exception:

gulp.task('less', function() { 
return gulp.src('less/*.less')
.pipe(less().on('error', gutil.log))
.pipe(gulp.dest('app/css'));
});

Then, you can use gulp-plumber plugin, which suppresses default error handler for all further plugins in a pipe() chain

gulp.task('less', function() {
return gulp.src('less/*.less')
.pipe(plumber())
.pipe(less())
.pipe(gulp.dest('app/css'));
});

Why needed another solution?

Despite of problem looks solved, it isn’t so. Now you will not receive any error message from build on CI-server. All errors now is being captured somewhere, so gulp thinks that all tasks has been passed right, even and some errors has occurred. Computers can’t read and understand console output, they use exit codes to tell the result of process and in that case exit code is zero which means “success”. And now we have a question how bring our error handlers back.

Getting errors properly

Actually, gulp can receive errors from pipeline. But only from the last one in chain, because streams designed to not propagate errors via pipes. Node.js repo has a pull-request with a related proposal, but it is not merged yet. So, now gulp can get only error from last stream, and usualy it is gulp.dest() call to write the result of task to file. But generally errors occur in other plugins between gulp.src and dest. Gulp cannot see them, but we can help him.

Gulp task function can accept not only streams as the result. We can define task in a classic node-style with callback in arguments. That function we can call manually and report erorrs from the middle pipes as well.

gulp.task('less', function(done) {
gulp.src('less/*.less')
.pipe(less()).on('error', function(error) {
// we have an error
done(error);
})
.pipe(gulp.dest(‘app/css’))
.on(‘end’, function() {
// in case of success
done();
});
});

That way to handle errors a bit more verbose, but correct. We can move common code in a little helper function like in this gist and then will get less code in task definition:

gulp.task(‘less’, wrapPipe(function(success, error) {
return gulp.src(‘less/*.less’)
.pipe(less().on(‘error’, error))
.pipe(gulp.dest(‘app/css’));
}));

Now we can keep calm for our build on CI-server and enjoy local-development as well.

Show your support

Clapping shows how much you appreciated Boris’s story.