I have an async middleware in express, because I want to use await inside it, to clean up my code.
const express = require('express');
const app = express();
app.use(async(req, res, next) => {
await authenticate(req);
next();
});
app.get('/route', async(req, res) => {
const result = await request('http://example.com');
res.end(result);
});
app.use((err, req, res, next) => {
console.error(err);
res
.status(500)
.end('error');
})
app.listen(8080);
The problem is that when it rejects, it doesn't go to my error middleware, but if I remove the async keyword and throw inside a middleware it does.
app.get('/route', (req, res, next) => {
throw new Error('Error');
res.end(result);
});
So I'm getting UnhandledPromiseRejectionWarning instead of entering my error handling middleware, how can I let the error bubble up, and express handle it?
The problem is that when it rejects, it doesn't go to my error middleware, but if I remove the async keyword and throw inside a middleware it does.
express doesn't support promises currently, support may come in the future release of express@5.x.x
So when you pass a middleware function, express will call it inside a try/catch block.
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
The problem is that try/catch won't catch a Promise rejection outside of an async function and since express does not add a .catch handler to the Promise returned by your middleware, you get an UnhandledPromiseRejectionWarning.
The easy way, is to add try/catch inside your middleware, and call next(err).
app.get('/route', async(req, res, next) => {
try {
const result = await request('http://example.com');
res.end(result);
} catch(err) {
next(err);
}
});
But if you have a lot of async middlewares, it may be a little repetitive.
Since I like my middlewares as clean as possible, and I usually let the errors bubble up, I use a wrapper around async middlewares, that will call next(err) if the promise is rejected, reaching the express error handler and avoiding UnhandledPromiseRejectionWarning
const asyncHandler = fn => (req, res, next) => {
return Promise
.resolve(fn(req, res, next))
.catch(next);
};
module.exports = asyncHandler;
Now you can call it like this:
app.use(asyncHandler(async(req, res, next) => {
await authenticate(req);
next();
}));
app.get('/async', asyncHandler(async(req, res) => {
const result = await request('http://example.com');
res.end(result);
}));
// Any rejection will go to the error handler
There are also some packages that can be used
Well, I found this - https://github.com/davidbanham/express-async-errors/, then require the script and you are good to go
const express = require('express');
require('express-async-errors');
Answer with asyncHandler is good and usefull, but it is still not comfortable to write this wrapper in every route. I propose to improve it:
const asyncHandler = fn => (req, res, next) => {
return Promise
.resolve(fn(req, res, next))
.catch(next)
}
const methods = [
'get',
'post',
'delete' // & etc.
]
function toAsyncRouter(router) {
for (let key in router) {
if (methods.includes(key)) {
let method = router[key]
router[key] = (path, ...callbacks) => method.call(router, path, ...callbacks.map(cb => asyncHandler(cb)))
}
}
return router
}
and now we can do that way:
const router = toAsyncRouter(express().Router())
router.get('/', someAsyncController)
and so one.
Minute ago added a npm module async-express-decorator.