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.
Related
I'm using Nodejs Express, the router middleware, in this case, MongoDB and using the POST method via JQuery ajax. Does anyone know why the
router.post doesn't call, but a .get works.
Does anyone know why the router.post
doesn't get triggered when called as a Promise/async.
Async route here doesn't get called on ajax post.
async function asyncMain() {
const uri = "mongodb endpoint";
const client = new MongoClient(uri)
try {
await client.connect().then(console.log('Connected to MongoDB'));
//keeps awaiting cause it never executes.
await pushToDB(client)
//all mongoClient functions work with await here
//Express http Post doesn't work with await here
} catch (e) {
console.error(e);
} finally {
await client.close();
}
}
asyncMain().catch(console.error);
async function pushToDB(client) {
router.post('/', async(req, res, next) => {
const result = await client.db('user').collection('notes').insertOne(req.body);
console.log(`Results ${ result.insertedId }`);
})
}
I tried using this async Middleware implementation with no results.
Such as wrapping the post function's callback in asyncMiddleware to no avail.
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
My working sync function for reference.
function main() {
const uri = "mongodb endpoint";
const client = new MongoClient(uri)
client.connect().then(console.log('connected to DB'));
router.post('/', (req, res) => {
const result = client.db('user').collection('notes').insertOne(req.body);
console.log(`Results ${ result.insertedId }`);
})
}
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.
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.
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.
I'm writing some rendering-code for an Express app, I wish to catch errors and then output them in the function render, but I'm not sure how I'm going to move them from one method to the other.
app.get('/user/makeRider', auth,
(req, res, next) => {
req.user.user.makeRider(req.query)
.catch(error)
.then(render(req, res));
}
);
var render = (req, res) => {
var response = {
params: req.query,
user: req.user.fulluser
};
res.json(response);
},
error = (reason) => {
reason.errors.forEach((error) =>{
console.log(error);
});
return;
};
You can use error function as your last midleware in the chain and simply pass the request to the next chain:
var render = (req, res) => {
var response = {
params: req.query,
user: req.user.fulluser
};
res.json(response);
}
app.get('/user/makeRider', auth,
(req, res, next) => {
req.user.user.makeRider(req.query)
.catch(next)
.then(render(req, res));
}
);
app.use((reason, req, res, next) => {
res.send(reason.errors);
// or you can send them as json: res.status(404).json({errors: reason.errors})
});
Beware of hoisting issue in your code, the variable declarations are hoisted to the top, but not their assignments, error and render function may appear as undefined when accessed from your route.
A quick, but maybe not the most elegant solution would be to add errors as a parameter to your render function, then you could do something like this:
app.get('/user/makeRider', auth,
(req, res, next) => {
req.user.user.makeRider(req.query)
.catch((reason)=>{
render(req, res, reason.errors)
})
.then(render(req, res));
}
);
var render = (req, res, errs) => {
var response = {
params: req.query,
user: req.user.fulluser
};
res.json(response);
};