Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jackson Dowell
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jackson Dowell

Bind Your Error Handler Before Your Readable Handler When Using Node.js Streams

By
Published in Comments (8)

If you've been using Node.js for a while, streams are probably second nature to you; but, for me, streams have proven to be quite a hurdle with many facets that don't seem immediately obvious. One such concept that recently tripped me up was the importance of binding your "error" event handler before you bind your "readable" event handler.

JavaScript makes heavy use of event-driven programming. And, much of the time (though not always), event handlers are invoked asynchronously after they are first bound. This is especially true if you deal with Promise libraries like Q or $watch() handlers in AngularJS. As such, it's easy to forget that the asynchronous nature of event bindings is explicit characteristic, not an implicit one.

As I've started learning about Node.js streams, I've come to find out that the "readable" event appears to invoke its callback synchronously, which means that the "error" event handler has to be bound prior to the "readable" event in order prevent errors from becoming exceptions that crash the process. To see this in action, I've created a tiny demo that defines a Readable stream that emits an error when trying to populate the internal buffer:

// Include module references.
var stream = require( "stream" );
var util = require( "util" );
var chalk = require( "chalk" );


// ---------------------------------------------------------- //
// ---------------------------------------------------------- //


// I am a readable stream, running with default option.
function DataStream() {

	stream.Readable.call( this );

}

util.inherits( DataStream, stream.Readable );


// I populate the internal stream buffer.
// --
// NOTE: For this experiment, all calls to this will emit an error to demonstrate
// the important of event-binding ordering.
DataStream.prototype._read = function( sizeIgnored ) {

	this.emit( "error", new Error( "SynchronousError" ) );

	this.push( null );

};


// ---------------------------------------------------------- //
// ---------------------------------------------------------- //


var dataStream = new DataStream();

// When the readable stream is readable, start reading data.
dataStream.on(
	"readable",
	function handleDataStreamReadable() {

		var data = null;

		// Keep reading from the internal buffer while data is available.
		while ( data = this.read() ) {

			console.log( chalk.cyan( "Readable event:", data ) );

		}

	}
);

// Bind to the error event so the stream won't raise exceptions.
dataStream.on(
	"error",
	function handleDataStreamError( error ) {

		console.log( chalk.bgRed.white( "Error event:", error.message ) );

	}
);

Notice that the "readable" event handler is bound before the "error" event handler. When we run this code through Node.js, we get the following terminal output:

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

As you can see, our error event is crashing the Node.js process because the prototypal EventEmitter thinks that no error event handlers have been bound at the time of the error event.

Now, let's take the same exact code and do nothing but switch the order of the event bindings. This time, I'm going to bind the "error" event handler before the "readable" event handler:

// Include module references.
var stream = require( "stream" );
var util = require( "util" );
var chalk = require( "chalk" );


// ---------------------------------------------------------- //
// ---------------------------------------------------------- //


// I am a readable stream, running with default option.
function DataStream() {

	stream.Readable.call( this );

}

util.inherits( DataStream, stream.Readable );


// I populate the internal stream buffer.
// --
// NOTE: For this experiment, all calls to this will emit an error to demonstrate
// the important of event-binding ordering.
DataStream.prototype._read = function( sizeIgnored ) {

	this.emit( "error", new Error( "SynchronousError" ) );

	this.push( null );

};


// ---------------------------------------------------------- //
// ---------------------------------------------------------- //


var dataStream = new DataStream();

// Bind to the error event so the stream won't raise exceptions.
dataStream.on(
	"error",
	function handleDataStreamError( error ) {

		console.log( chalk.bgRed.white( "Error event:", error.message ) );

	}
);

// When the readable stream is readable, start reading data.
dataStream.on(
	"readable",
	function handleDataStreamReadable() {

		var data = null;

		// Keep reading from the internal buffer while data is available.
		while ( data = this.read() ) {

			console.log( chalk.cyan( "Readable event:", data ) );

		}

	}
);

Now, when we run this through Node.js, we get the following terminal output:

Error event: SynchronousError

This time, our event handler "caught" the "error" event and was able to log information to the standard output without crashing the Node.js process.

Since one of the core philosophies of Node.js is non-blocking I/O, it's easy to think that everything happens asynchronously. But this is clearly not the case. When dealing with Node.js streams (v0.10.26), "error" handlers need to be bound before "readable" handlers because error events may not happen asynchronously.

Want to use code from this post? Check out the license.

Reader Comments

15,848 Comments

@Ray,

Not yet. You had mentioned it before and I went to look at it. I didn't know anything about NodeSchool, so I assumed it was a video course. But, it looks like it's an interactive REPL adventure :D I was a little thrown by that - just wasn't what I was expecting. I'm planning on doing it, but just haven't started yet.

357 Comments

To be honest, the whole "adventure" thing isn't really.. an adventure. Literally it doesn't come into play at all. You have N challenges and that's it. Now - they are *very* good challenges I think, don't get me wrong, but it isn't really a game per se.

15,848 Comments

@Ilya,

Ah, groovy. I'm still on 0.10.32 locally. Out of curiosity, do you know if Node.js does that thing where odd-numbers (ie, 11) are not-stable or production-ready? I think they used to do that - not sure if that approach is still being used.

15,848 Comments

@Ilya,

Ah groovy, thanks. For me personally, it's not too much of a concern, since I mostly use Node.js to run Gulp.js. I don't actually run any node in production... yet :)

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel