Reputation: 40444
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?
Upvotes: 88
Views: 66931
Reputation: 40444
Update:
express@5
was just released with support for async routes/middlewares.
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 [email protected]
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
Upvotes: 142
Reputation: 11069
Express 5 now handle async promises:
https://expressjs.com/en/guide/error-handling.html
Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example
Upvotes: 8
Reputation: 41
You need to callbackify your async handler. If you know the concept of promisify, this is the opposite. Callbackify is built-in in Node.
import util from 'util'
app.use(util.callbackify(async (req, res) => {
await authenticate(req);
}));
What this does is that it returns a function with a third argument which would be the next function and calls it after the promise has been resolved. If the promise is rejected, the next function will be called with the error as an argument.
Upvotes: 4
Reputation: 1595
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');
Upvotes: 50
Reputation: 1180
You need to use try-catch and in catch section just pass the error in next() parameter Like this -
async create(req, res, next) {
try {
const userProp = req.body;
const user = new User(userProp)
const response = await user.save()
const token = await user.createJWSToken()
res.send({response, token})
} catch (err){
next(err)
}
}
And obviously put this express middleware on your index.js file.
app.use((err, req, res, next) => {
res.status(422).send({ error: err.message });
});
Upvotes: 7
Reputation: 201
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.
Upvotes: 20