4 min read

Node.js Error Handling Patterns

node.js exception

The WRONG Way

There is always a exceptionally bad way of doing something in NodeJS (or any language) so I figured covering this will put perspective on the alternatives.

Listening for uncaughtException

Listening for events on the global process variable is easy, and this is likely why we see this so often in Node.js projects.

Take the following example if you are unfamiliar;

process.on('uncaughtException', function(err) {
	console.log('Threw Exception: ', err);
});

So what exactly is wrong with this you ask? Simply put, after this event is emitted Node.js would usually crash and need to be restarted which keeps your software state as expected, whereas if you were to implement this On Error Resume Next equivalent your software will enter an unknown state and bad things WILL happen.

Restart your application after every uncaughtException!

Node.js officially warn of the harm when listening for uncaughtException on the global process variable and advise to use use domains which is covered further in this article.

Using Modules

A graceful shutdown is the best you can hope for when you encounter uncaughtException, graceful meaning you save all in-memory data.

NPM modules such as monit, forever, or upstart can be used to restart node process gracefully when uncaughtException is emitted.

Using Node.js Domain

This is the recommended pattern by Node.js.

Wrap a section of code in a node.js domain like this;

var domain = require('domain').create();
domain.on('error', function(err){
	console.log(err);
});

domain.run(function(){
	throw new Error('thwump');
});

This new Node.js feature is currently unstable so use with caution if deploying to production.

Designed to work for asynchronous or synchronous code blocks, you are able to handle expections in context as opposed to uncaughtException which looses the exception context.

Old Node.js version? domain feature not available? Try/Catch

Now the really interesting thing about using domains is it is closely tied to the cluster module, meaning it is actually possible to treat each connection (user) state individually, therefore allowing Node.js to operate uneffected connections until(if) they themselves encounter the exception the first user encountered. Of course you will have to refuse new connections until you have restarted the Node.js process, as well as handle that user(s) who triggered the error.

Try/Catch

The most common exception handling in Node.js (most languages) is Try/Catch.

But I read JavaScript Try/Catch performance is bad I hear you say, well you read correct (sort of)

This performance problem is mainly in context to JavaScript running in the browser not in Node.js which is v8, unfortunately there are gotchas with v8 also.

To shorten this, use Try/Catch outside your functions for best results.

try {
    throw new Error('thwump');
} catch (e) {
    console.log(e);
}

Finally: only use Try/Catch for synchronous code.

Functional Approach

An example using the Express.js framework, define a function that returns an error handler:

function error(res,next) {
  return function( err, result ) {
    if( err ) {
      res.writeHead(500); res.end(); console.log(err);
    }
    else {
      next(result)
    }
  }
}

Which allows you to do this;

app.get('/foo', function(req,res) {
  db.query('sql', error(res, function(result) {
    // use result
  }));
});

Covering this method only as an example, do not use this method for production code, unless you actually handle the error properly.

Chain Of Responsibility - Your Error Handler

Rule of thumb: Never, ever!, deal with the error you don't know, pass it to next callback, or throw it.

Otherwise according to the type of error which you know, design your error handler to correct the problem so the process may continue to the succeeding callback.

Safely throwing Errors

Instead of literally throwing the error, you design your error handler in such a way to allow for continuance if the error thrown can be corrected.

For Synchronous Code

If an error happens, return the error:

var match = function match(foo,bar) {
    if ( foo !== bar ) {
        return new Error("no match");
    } else {
        return true;
    }
};
var result = match(1,2);
if ( result instanceof Error ) {
    console.log('Error: ', result);
} else {
    console.log('Result: ', result);
}

Realistically this function would return false rather than an instance of Error, but you get the point.

For Callback Based (Asynchronous) Code

There is a common and well known pattern for handling errors in Node.js.

var match = function match(foo,bar,next) {
    if ( foo !== bar ) {
        next( new Error("no match") );
    } else {
    	var result = true;
        next(null, result);
    }
};
match(1, 2, function handleMatch(err,result){
  if ( err ) {
  	console.log('Error: ', err);
  } else {
  	console.log('Result: ', result);
  }
});

The first argument of the callback is always err.
So you know always that if err is Error, handle it, or if err is null you can expect any other arguments to follow the err argument.

For eventful Code

With the event coding pattern the error may happen anywhere, so instead of throwing the error one would emit the error event instead:

var events = require('events');
var Matcher = function Matcher(){
    events.EventEmitter.call(this);
};
require('util').inherits(Matcher, events.EventEmitter);

Matcher.prototype.match = function match(foo,bar) {
  if ( foo !== bar ) {
  	this.emit('error', new Error("no match"));
  } else {
  	this.emit('match', foo, bar, true);
  }
  return this;
};
var matcher = new Matcher();
matcher.on('error', function(err){
    console.log(err);
});
matcher.on('match', function(foo, bar, result){
    console.log('Match: ', result);
});
matcher.match(1,2).match().match(1,1).match(1);

Here I defined the Matcher Event Emitter, a method called match with the functionality, emitting an error or match event depending ont he outcome, and finally return itself for chaining.

Conclusion

Discovering the cause of a crash in Node.js doesn't have to be terrible, if you have a good sense of error correction and reporting.
The end result is a stable program and excellent exposure if problems do arise.

Remember to refer to Node.js frequently and always restart your application after every uncaught, unhandled, or unknown exceptions! Once the cause is ascertained and corrected of course.

Enjoyed this?

If you made it here I would love to hear what you thought of the article.