Catching all errors in async-await express route handlers - javascript

Suppose I have a route like this:
app.get('/broken', (req, res) => {
throw new Error('Broken!');
});
This will never send a response to clients.
However, I can add a middleware for all errors:
const errorMiddleware = (error, req, res, next) => {
if (error) {
console.error(error);
return res.status(500)
.json({
message: 'Internal server error',
});
}
next(error);
};
But this will not work for async routes, because they do not throw directly.
For example, this will not work:
app.get('/broken', async (req, res) => {
throw new Error('Broken!');
});
So I can create a wrapper like this:
const asyncRoute = f => (req, res, next) => {
return Promise.resolve(f(req, res, next)).catch(next);
};
app.get('/broken', asyncRoute(async (req, res) => {
throw new Error('Broken!');
}));
But this is a real pain, because now I have to call this function for every route!
What is a better way of handling this?
The answer to Is there a way to wrap an await/async try/catch block to every function? is just what I describe above
The answer to how to use Promise with express in node.js? does not use await

Fundamentally, you don't want to directly pass an async function into Express's app.get, because app.get doesn't handle the promise the function returns. So you'll need to wrap those async handlers (as you're doing).
You can avoid having to do it every time by giving yourself a utility method at the top of the module:
const appGet = handler => app.get(asyncRoute(handler));
then use it instead of app.get:
appGet('/broken', async (req, res) => {
throw new Error('Broken!');
});
At some point (probably not right now), you might want to look at Koa.

Related

I don't understand the syntax of the following javaScript code

else{
passport.authenticate("local")(req, res, function () {
res.redirect("/secrets");
});
}
Why is there no "." after authenticate("local") (here)and before (req, res, function () .........)
passport.authenticate returns a function. That returned function can then be called (like any function) - req, res, and another callback function are arguments that can be passed.
If you're finding it difficult to read, it might make more sense to break the returned function out into its own identifier.
const passportHandler = passport.authenticate("local");
passportHandler(req, res, function () {
res.redirect("/secrets");
});
Often, an even better approach would be to for the Passport middleware to be declared for the route itself, instead of declaring both a route callback and then passing down the req and res again. That is
app.post(
'someEndpoint',
passport.authenticate("local"),
(req, res) => {
res.redirect("/secrets");
}
)
if you can figure out a way around the else. passport.authenticate returns a route handler callback, and the (req, res) => is also a route handler callback..

AsyncMiddleware handler in node js

I have read promise/resolve/reject as well as async/await.
I want to handle async/await error and found a code on medium.com but I am not able to understand what exactly it does.
Can anyone please try to explain how below code works:
a) what is fn here?
b) I actually can't understand any code from the below block.
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
And using it as below:
router.get('/users/:id', asyncMiddleware(async (req, res, next) => {
/*
if there is an error thrown in getUserFromDb, asyncMiddleware
will pass it to next() and express will handle the error;
*/
const user = await getUserFromDb({ id: req.params.id })
res.json(user);
}));
It the same as:
// asyncMiddleware is function that returns another function
const asyncMiddleware = function(fn){
return (req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
}
It is just ES6 syntax. Try to read about how things are written in ES6.
fn is a function which is being given as a argument to asyncMiddleware function. The fn function returns a promise which is being resolved in the line
Promise.resolve(fn(req, res, next)).if any error is encountered in fn, it will go to catch and error is handled.
Hope it helps you.

express.js async router and error handling

I have an async function as a route handler, and i'd like to have errors handled as some kind of middleware. Here is my working attempt:
router.get(
"/",
asyncMiddleware(
routeProviderMiddleware(
async ({ y }) => ({
body: await db.query({x: y})
})
)
)
)
// This is the middleware that catches any errors from the business logic and calls next to render the error page
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next)
}
// This is a middleware that provides the route handler with the query and maybe some other services that I don't want the route handler to explicitly access to
const routeProviderMiddleware = routeHandlerFn => async (req, res) => {
const {status = 200, body = {}} = await routeHandlerFn(req.query)
res.status(status).json(body)
}
What I strive to is a way to make the route declaration cleaner - I don't want the 2 middleware wrappers there, ideally i'd like for the business logic function there only, and somehow declare that every route is wrapped in these.
Even combining the two middlewares together would be nice, but I didn't manage.
I use following approach:
Create asyncWrap as helper middleware:
const asyncWrap = fn =>
function asyncUtilWrap (req, res, next, ...args) {
const fnReturn = fn(req, res, next, ...args)
return Promise.resolve(fnReturn).catch(next)
}
module.exports = asyncWrap
All your routes/middlewares/controllers should use this asyncWrap to handle errors:
router.get('/', asyncWrap(async (req, res, next) => {
let result = await db.query({x: y})
res.send(result)
}));
At app.js, the last middleware will receive the errors of all asyncWrap:
// 500 Internal Errors
app.use((err, req, res, next) => {
res.status(err.status || 500)
res.send({
message: err.message,
errors: err.errors,
})
})
Express 5 automatically handles async errors correctly
https://expressjs.com/en/guide/error-handling.html currently says it clearly:
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:
app.get('/user/:id', async function (req, res, next) {
var user = await getUserById(req.params.id)
res.send(user)
})
If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.
I have shown that in an experiment at: Passing in Async functions to Node.js Express.js router
This means that you will be able to just make the callback async and use await from it directly without any extra wrappers:
router.get("/", async (req, res) =>
const obj = await db.query({x: req.params.id})
// Use obj normally.
)
and errors will be correctly handled automatically.
Express permits a list of middlewares for a route and this approach sometimes works for me better than higher-order functions (they sometimes look like an overengineering).
Example:
app.get('/',
validate,
process,
serveJson)
function validate(req, res, next) {
const query = req.query;
if (isEmpty(query)) {
return res.status(400).end();
}
res.locals.y = query;
next();
}
function process(req, res, next) {
Promise.resolve()
.then(async () => {
res.locals.data = await db.query({x: res.locals.y});
next();
})
.catch((err) =>
res.status(503).end()
);
}
function serveJson(req, res, next) {
res.status(200).json(res.locals.data);
}
What you can do is add an error handlers after your routes. https://expressjs.com/en/guide/error-handling.html
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
What I ended up doing is unifying the wrappers like this:
const routeProvider = routeHandlerFn => async (req, res, next) => {
try {
const {status = 200, body = {}} = await routeHandlerFn(req.query)
res.status(status).json(body)
} catch(error) {
next(error)
}
}
This wrapper is all any route would need. It catches unexpected errors and provides the route handler with the needed params.

Handling errors in express async middleware

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
async-middleware
express-async-handler
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.
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 });
});
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
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.

when you should handle error in express

When to skip error handling? I don't think this make sense
exports function getItems(req, res, next) => {
Item.find({}, function(err, items){
if(err) throw Error()
res.json(items)
})
}
Because get will not fail most of the time. I'm sick of writing error handling in node. Now I skip GET request, just do handling for POST, PUT or DELETE.
I'm using async await, I have to do try catch in every GET, that's annoying.
router.get('/user/:id', async (req, res, next) => {
try {
const user = await getUserFromDb({ id: req.params.id })
res.json(user);
} catch (e) {
next(e)
}
})

Categories