Error handling principles for Node.js + Express.js applications?
Error handling in Node.js is generally of the format A). Most callbacks return an error object as the first argument or null
.
Express.js uses middleware and the middleware syntax uses B) and E) (mentioned below).
C) is bad practice if you ask me.
app.get('/home', function(req, res) {
// An error occurs
throw err;
});
You can easily rewrite the above as
app.get('/home', function(req, res, next) {
// An error occurs
next(err);
});
Middleware syntax is valid in a get
request.
As for D)
(07:26:37 PM) tjholowaychuk: app.error is removed in 3.x
TJ just confirmed that app.error
is deprecated in favor of E
E)
app.use(function(err, req, res, next) {
// Only handle `next(err)` calls
});
Any middleware that has a length of 4 (4 arguments) is considered error middleware. When one calls next(err)
connect goes and calls error-based middleware.
People at Joyent have published a really insightful best-practices document on this. A must-read article for any Node.js developer.
Why first-parameter?
Because of the asynchronous nature of Node.js, the first-parameter-as-err pattern has become well established as a convention for userland Node.js error handling. This is because asynchronous:
try {
setTimeout(function() {
throw 'something broke' //Some random error
}, 5)
}
catch(e) {
//Will never get caught
}
So instead having the first argument of the callback is pretty much the only sensible way to pass errors asynchronously other than just throwing them.
To do so will result in an unhandled exception
which, just in the way it sounds, implies that nothing was done to get the application out of its confused state.
Exceptions, why do they exist
It is worth noting however, that virtually all part of Node.js are event-emitters and the throwing of an exception is a low-level event which can be handled like all events:
//This won't immediately crash if connection fails
var socket = require("net").createConnection(5000);
socket.on("error", function(err) {
console.error("calm down...", err)
});
This can-but-shouldn't be taken to the extreme to catch all errors and make an application which will try very hard to never crash. This is a terrible idea in nearly every use-case, because it will leave the developer without any idea of what's going on in the application state and is analogous to wrapping main in try-catch.
Domains - grouping events logically
As part of dealing with this problem of exceptions making applications fall over, domains allow the developer to take, for example the Express.js application, and try and close off connections sensibly in the event of catastrophic failure.
ES6
It's probably mentioning that this will change again as ES6 allows the generator pattern to create asynchronous events which are still catchable with try/catch blocks.
Koa (written by TJ Holowaychuck, same original author of Express.js) noticeably does this. It uses the ES6 yield
statement to create blocks that, while appearing nearly syncronous, are handled in the usual node asynchronous fashion:
app.use(function *(next) {
try {
yield next;
}
catch (err) {
this.status = err.status || 500;
this.body = err.message;
this.app.emit('error', err, this);
}
});
app.use(function *(next) {
throw new Error('some error');
})
This example was shamelessly stolen from here.