The trouble with Express.js error handling (draft)
March 28, 2020
Async Code and Express.js
If a promise is rejected in Express, unless it’s handled explicitly, node will show an unhandled promise exception and the browser will hang, not showing any response from the server.
References
[1] Express error handling documentation https://expressjs.com/en/guide/error-handling.html
- Errors that occur in synchronous code inside route handlers and middleware require no extra work. If synchronous code throws an error, then Express will catch and process it.
- Shows the use of a package called method-override without saying anything about it in the text.
- There is some noise in the examples, for example the use of methodOverride() which is irrelevant to the examople code.
- It is not clear at all, but the custom error handler must come after the route handlers. This is discussed here https://stackoverflow.com/questions/29700005/express-4-middleware-error-handler-not-being-called
[2] Express 5 (in alpha since 2014) adds support for returns and rejected promises in the router. https://github.com/expressjs/express/blob/5.0/History.md
[3] Express 5 tracking issue https://github.com/expressjs/express/pull/2237
[4] Express 5 Promises https://github.com/expressjs/express/issues/2259
[5] Good article on how to improvess Express error handling https://nemethgergely.com/error-handling-express-async-await/
- Shows how to return an error object with httpStatusCode.
- Shows an example of how to wrap the request handler in an method to catch rejected promises.
[6] Article about “async middleware” https://dev.to/geoff/writing-asyncawait-middleware-in-express-6i0
[7] Good article about error handling in express https://thecodebarbarian.com/80-20-guide-to-express-error-handling
[8] Another article about error handling in express https://www.robinwieruch.de/node-express-error-handling
Error handling
Express requires that errors are passed to the next
function, which is one of the
inputs to the request handler.
app.post("/checkout", async (req, res, next) => {
let responseData;
try {
responseData = await axios.get("https://example.com/give-me-an-error");
} catch (e) {
return next(e);
}
}
And then you define an error handler:
// Error handler.
// eslint-disable-next-line no-unused-vars
app.use(function (err, req, res, next) {
log.err(err.stack);
res.status(500).send('Something went wrong, sorry.');
});
The problem
This is really verbose:
try {
await webShopApi.finalizeOrder(order);
} catch (e) {
return next(e);
}
This works, but doesn’t stop execution (so can only be used when execution ends anyway).
await webShopApi.finalizeOrder(order).catch(e);
Written by Jim Pickard who runs a pub by day and develops software by night. You can follow him on Twitter