Async/Await in Express Middleware - javascript

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?

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.

You can use lib for this express-async-errors. It patchs express with no problems.

S.Nakib has a great answer.
I included the asyncHandler in the utils folder of my project which I use across all files and use it in the async middlewares
utils.js contains
const asyncHandler = fn => (req, res, next) => {
fn(req, res, next).catch(next)
}
other.js contains
async function someAuthAsyncFunction(req, res, next) {
await callToOtherService()
next()
}
app.js contains
app.use(utils.asyncHandler(other.someAuthAsyncFunction))
This is worked for me as I'm working with routes and just a few middlewares are async

Related

Execution flow using next()

I am new to express and have a question about mechanics of next() function.
Am I correct that once next() is called it immediately triggers execution of app.get, whilst everything below next() will be executed asynchronously?
If so, why 'Am I executed?' is not printed to console once I put big delay in setTimeout()?
Please explain execution flow in the code below.
app.param('seriesId', (req, res, next) => {
... // Check for presence of series
console.log('I am executed');
next();
setTimeout(() => {console.log('Am I executed?')}, 1000); // Prints for 100, does not print for 1000
});
app.get('/:seriesId', (req, res, next) => {
... // Call to db to get series object
res.status(200).json({series: series});
});
Calling next() will handle control over to the next middleware in the pipe. In your example, this would be the app.get.
However, the method does not behave like a return statement, so any code you put after, will get executed too.
Given the example below, if you would start the server and navigate to http://localhost:1337/foo, the log statements would be:
well here we are
executing the get
const express = require('express');
const app = express();
app.param('param',(req, res, next) => {
next();
setTimeout(() => console.log('well here we are'), 1000);
});
app.get('/:param', (req, res) => {
setTimeout(() => {
console.log('executing the get');
res.status(200).send();
}, 2000);
});
app.listen(1337);
console.log('app started at http://localhost:1337');
Branching in middleware
A good practice to avoid confusion, is to make sure calls to next() are placed at the end of your execution. For example, don't do this:
if(aCondition) {
next()
}
next(new Error('Condition was false'));
But do:
if(aCondition) {
next()
} else {
next(new Error('Condition was false'));
}
Alternatively, what I do is always return next() calls, to avoid middleware from executing any further code.
Executing async code in middleware
And a final remark: if you need to execute asynchronous code in your middleware, then only call next() once this code has finished executing.
Don't do:
loadUserFromDB()
.then(u => req.user = u);
next();
But do:
loadUserFromDB()
.then(u => {
req.user = u;
next();
});

chain middleware functions in custom function

I know that I can chain middleware functions after passing in the route like
const express = require('express');
const router = express.Router();
router.post('/', middlewareFunction1, middlewareFunction2, controllerFunction);
module.exports = router;
I would like to know if it's possible to call only one function (called gateway)
router.post('/', gatewayFunction1);
and this function is able to chain all those methods
const controller = require('../controllers/controller');
function gatewayFunction1(request, response, next) {
// validate route
// do other middleware stuff
// call controller.function1
}
module.exports = {
gatewayFunction1,
};
Why would I do that? I was thinking about separating the middleware logic from the routes. This gateway should just get executed after routing and before calling the router.
I tried to return an array of functions (example code)
function gatewayFunction1(request, response, next) {
return [(request, response, next) => {
console.log('called middleware 1');
next();
}, (request, response, next) => {
console.log('called middleware 2');
next();
}, (request, response, next) => {
response.status(200).json({
message: 'that worked',
});
}];
}
but when I call this api route I get no response
Could not get any response
so it keeps loading forever. Is there a way to chain these middleware functions within another function?
Your gatewayFunction1 does nothing except returns an array.
Just use router.
const express = require('express');
const gatewayFunction1 = express.Router();
gatewayFunction1.use(middlewareFunction1, middlewareFunction2, controllerFunction);
module.exports = gatewayFunction1;
Then
const gatewayFunction1 = require('...'); // where you define gatewayFunction1
router.post('/', gatewayFunction1);
Middleware should be a function and you are returning an array.If next function is not called it will get stuck. I don't like the whole idea combining them but I think the best way is to import all your middleware functions in one function and call them individually then use that function as your combined middleware.

Using Express module as middleware

I'm new to Express and trying to use middleware to handle a POST request. If I expose the endpoint, and make a request to the API, everything works fine.
Working Correctly
api/index.js
app.post('/api/endpoint', (req, res, next) => {
next();
});
server.js
app.use(function() {
console.log('hello'); // => hello
});
But when I try to replace the middleware function with a module that exports a function, the function never gets invoked.
Not Working
api/index.js
app.post('/api/endpoint', (req, res, next) => {
next();
});
server.js
const makeExternalRequest = require('./server/makeExternalRequest');
...
console.log(makeExternalRequest, typeof makeExternalRequest);
// => [Function] 'function'
app.use(makeExternalRequest);
server/makeExternalRequest.js
module.exports = function(err, req, res, next) {
console.log('hello', err);
}
The function in server/makeExternalRequest.js is never invoked, and nothing logs... Am I using app.use(...) incorrectly?
Express middleware requires three arguments, the third of which is a function you call when you're done to move the request along to the next handler:
module.exports = function (req, res, next) {
console.log('hello');
next();
};
Without calling the third parameter, your request will just remain pending and a response will never be sent. Also, be sure you call app.use before any handler that would return the response. If the response is sent first, then your middleware will never be reached.

Manually chaining Express middleware

I currently use 2 middlewares:
Express-jwt which extracts/validates a JsonWebToken from a request and my own middleware that checks that the JWT contains specific information (permissions).
I want to conditionally use those middlewares together (based on whether there's a specific swagger attribute on a route).
I want to do something like this:
let expressjwt = function(req, res, next) { ... };
let jwtValidator = function(req, res, next) { ... };
app.use((res, req, next) => {
if(req.swagger.someAttribute) {
expressjwt(req, res, jwtValidator(req, res, next));
// The issue here is that jwtValidator will get called even if
// expressjwt produces an error
} else {
next();
}
});
It sounds like the question is - "how do you conditionally call service B only if service A succeeds."
This is one of main goals of promises - it allows you to chain together async calls and have them conditionally "resolve." I can post a code sample if needed.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
I ended up wrapping my first middleware in a Promise using Promise.fromCallback, from memory, something like this:
if (req.swagger.someAttribute) {
Promise.fromCallback(cb => expressjwt(req, res, cb))
.then(() => {
return jwtValidator(req, res, next);
})
.catch(next); // Or deal with the rejection
} else {
next();
}
Promise.fromCallback is useful because next() is only called with arguments if the middleware failed and thus will become promise.reject

It is possible to enhance the express.js req and res variables without using a middleware function?

I'm working in a restful service using express.js and i want to enhance the req and res variables so for example you could write something like
app.use(function (req, res, next) {
res.Ok = function (data) {
res.status(200).send(data);
};
res.InternalError = function (err) {
res.status(500).send(err);
};
});
And later
router.get('/foo', function (req, res) {
res.Ok('foo');
})
This will send 'foo' in the body of the response and set the status code to 200 and is working perfectly.
My first question is if it is possible to add such functionality without a middleware function, lets say in a property or the prototype of the app variable?
The second question is if there are performance issues if you add many functionality with middleware functions at the app level. Are this functions attached to the request and response object per request or once on the application startup?
I know the Sails framework already do this but I'm wondering if they use middleware functions as well.
I keep digging and turns out that the request and response object are exposed in express using the __proto__ property.
var express = require('express'),
app = express();
app.response.__proto__.foo = function (data) {
this.status(200).send(data);
};
And later in the router
router.get('/foo', function (req, res, next) {
res.foo('test');
});
This will print test in your browser so it is possible to add functionality without using any middleware.
Note: I'm sure there are some drawbacks to this approach (overwriting express predefined properties, for example) but for testing purposes and adding very simple functionality I think is slightly better in terms of performance.
I'm not aware of any other way than using middleware. But in my opinion you could do the following to achieve nearly the same thing.
// Some Route
router.get('/foo', function(req, res, next) {
// ...
if(err) {
res.status(500);
return next(err);
}
return res.send('ok');
});
// Another route
router.get('/bar', function(req, res, next) {
// ...
if(badUserId) {
res.status(400);
return next('Invalid userId.');
}
req.result = 'hello';
return next();
});
router.use(function(req, res) {
// I prefer to send the result in the route but an
// approach like this could work
return res.send(req.result);
});
// Error Middleware
router.use(function(err, req, res, next) {
if(res.statusCode === 500) {
// Log the error here
return res.send('Internal server error');
} else {
return res.send(err);
}
});

Categories