It is a crucial part to have an excellent strategy for handling errors in your application. Not just for identifying bugs quickly but also for propagating sane messages to the user in the case when an operation has failed. I guess propagating is the correct term in this context. If you build an application architecture which consists of different layers and orchestrates several third-party modules, you will be confronted with the same question over and over again: How to handle those different errors from the lower layers correctly and make my application more robust?
One pattern that I use in my applications and which is also highly recommended by Joyent (see Error Handling in Node.js) is to wrap error objects instead of just passing them to the callback. A simple passing without augmenting the error can be stated as an anti-pattern. An example:
'use strict';
var fs = require('fs');
exports.readConfig = function readConfig (file, callback) {
function onRead (err) {
if (err) {
//
// Anti-Pattern alert!
// Don't just spit the
// "raw" error object back.
//
return callback(err);
}
}
fs.readFile(file, onRead);
};
A much cleaner approach would be to create a new error object and extend it by the thrown message. This is where verror by David Pacheco enters the stage. It provides you the functionality for combining several error objects by preserving each one’s message. The result will be a concatenated message that gives you a good clue about the complete function chain. Therefore you will immediately know where something went wrong. Let’s check it out:
npm install verror
The module itself can be seen as a drop-in replacement of the native error object instances.
'use strict';
var fs = require('fs');
var VError = require('verror');
exports.readConfig = function readConfig (file, callback) {
function onRead (err, content) {
if (err) {
return callback(new VError(err, 'failed to read config file "%s"', file));
}
try {
content = JSON.parse(content);
} catch (e) {
return callback(new VError(e, 'failed to parse config file "%s"', file));
}
callback(null, content);
}
fs.readFile(file, onRead);
};
Please not that the module supports printf-style error message construction which is pretty cool. If we execute the above function as
exports.readConfig('/not/available.json', function onRead (err, config) {
if (err) {
return console.error(err.message);
}
...
});
it will obviously fail because of the non-existent configuration file. The printed message will then look like:
failed to read config file "/not/available.json": ENOENT, open '/not/available.json'
So if you pass the root cause to the VError
constructor, you will be able to receive the complete concatenated error message string. That’s awesome! But what if you only want to see the error message from the highest layer and still be able to access the low-level errors programmatically? verror provides another constructor for cases like that, called WError
. It wraps all error objects in the lower levels, but stores the message of the “highest one” in it’s message
attribute. If you have to access the other wrapped objects, you get the full details by calling toString()
on a WError
instance.
To see those both types in action, make sure to check out runnable example and the example repository.
What’s Next?
You have your very own best practices about handling errors in Node.js? I’m curious about those and would be more than happy to read about them in the comments 🙂