I'm having trouble understanding how to properly write middleware in Express that utilizes async/await, but doesn't leave a Promise floating in the ether after it's execution. I've read a ton of blogs and StackOverflow posts, and it seems like there is some consensus around using the following pattern in async/await middleware:
const asyncHandler = fn => (req, res, next) =>
Promise
.resolve(fn(req, res, next))
.catch(next)
app.use(asyncHandler(async (req, res, next) => {
req.user = await User.findUser(req.body.id);
next();
}));
I understand that this makes it possible to not have to use try..catch logic in all of your aysnc route-handlers, and that it ensures that the Promise returned by the (async (req, res, next) => {}) function is resolved, but my issue is that we are returning a Promise from the asyncHandler's Promise.resolve() call:
Promise
.resolve(fn(req, res, next))
.catch(next)
And never calling then() on this returned Promise. Is this pattern used because we aren't relying on any returned value from middleware functions? Is it OK to just return Promises and never call then() on them to get their value, since there is no meaningful value returned from middleware in Express?
I get that async/await allows us to deal with the async code and work with the returned values easily, but in Express middleware we are left with that top-level async, which resolves to a Promise, which we then resolve with Promise.resolve(), but which still resolves to a Promise...
Also, I understand that there are 3rd party solutions to this issue, and you could just use another framework like Koa. I just want to understand how to do this properly in Express, as I'm still relatively new to backend development with Node and want to focus solely on Express till I get the fundamentals down.
My tentative solution has been to use async/await only in non-middleware functions, and then just call then() on the returned Promises in the actual middleware, so that I can be sure I'm not doing anything naughty, like so:
app.use((req, res, next) => {
User.findUser(req.body.id)
.then(user => {
req.user = user;
next();
})
.catch(next)
});
Which is fine with me, but I keep see the asyncWrapper code all over the place. I'm over-thinking this right?
useP(async (req, res, next) => { req. user = await User. findUser(req.body.id); next(); }); And, any rejected promise would automatically be handled for you.
Unfortunately, Express will not be able to handle this error. You'll receive a log like this: To handle an error in an asynchronous function, you need to catch the error first. You can do this with try/catch .
With support for asynchronous functions (often referred to as async/await ) in Node. js as of v7. 6, we can now extract data directly from a resolved Promise in an async middleware function and pass that data to the final router callback in a clean and easily-readable manner.
Async functions return a Promise by default, so you can rewrite any callback based function to use Promises, then await their resolution. You can use the util. promisify function in Node. js to turn callback-based functions to return a Promise-based ones.
And never calling then() on this returned Promise. Is this pattern used because we aren't relying on any returned value from middleware functions?
Is it OK to just return Promises and never call then() on them to get their value, since there is no meaningful value returned from middleware in Express?
Yes, if all you want to track is whether it was rejected or not because it handles its own successful completion, but you need to handle an error separately, then you can just use .catch()
which is effectively what you're doing. This is fine.
If I was doing this a lot, I'd either switch to a promise-friendly framework like Koa or I'd add-on my own promise-aware middleware registration. For example, here's an add-on to Express that gives you promise-aware middleware:
// promise aware middleware registration
// supports optional path and 1 or more middleware functions
app.useP = function(...args) {
function wrap(fn) {
return async function(req, res, next) {
// catch both synchronous exceptions and asynchronous rejections
try {
await fn(req, res, next);
} catch(e) {
next(e);
}
}
}
// reconstruct arguments with wrapped functions
let newArgs = args.map(arg => {
if (typeof arg === "function") {
return wrap(arg);
} else {
return arg;
}
});
// register actual middleware with wrapped functions
app.use(...newArgs);
}
Then, to use this promise-aware middleware registration, you would just register it like this:
app.useP(async (req, res, next) => {
req.user = await User.findUser(req.body.id);
next();
});
And, any rejected promise would automatically be handled for you.
Here's a more advanced implementation. Put this in a file called express-p.js
:
const express = require('express');
// promise-aware handler substitute
function handleP(verb) {
return function (...args) {
function wrap(fn) {
return async function(req, res, next) {
// catch both synchronous exceptions and asynchronous rejections
try {
await fn(req, res, next);
} catch(e) {
next(e);
}
}
}
// reconstruct arguments with wrapped functions
let newArgs = args.map(arg => {
if (typeof arg === "function") {
return wrap(arg);
} else {
return arg;
}
});
// register actual middleware with wrapped functions
this[verb](...newArgs);
}
}
// modify prototypes for app and router
// to add useP, allP, getP, postP, optionsP, deleteP variants
["use", "all", "get", "post", "options", "delete"].forEach(verb => {
let handler = handleP(verb);
express.Router[verb + "P"] = handler;
express.application[verb + "P"] = handler;
});
module.exports = express;
Then, in your project, instead of this:
const express = require('express');
app.get(somePath, someFunc);
use this:
const express = require('./express-p.js');
app.getP(somePath, someFunc);
Then, you can freely use any of these methods and they automatically handle rejected promises returned from routes:
.useP()
.allP()
.getP()
.postP()
.deleteP()
.optionsP()
On either an app object you create or any router objects you create. This code modifies the prototypes so any app object or router objects you create after you load this module will automatically have all those promise-aware methods.
What you are doing is absolutely Ok. But for those who overthink, there is a simple solution. Just re-write the asyncHandler
.
const asyncHandler = fn => (req, res, next) => {
fn(req, res, next)
.catch(next);
}
We don't need to use Promise.resolve()
on the asyncHandler
. Since fn
is an async
function, it returns a promise. We can just catch()
that promise if there is an error inside the function.
And here we are not returning anything from asyncHandler
function since we don't need to.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With