Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Express middleware, next and Promises

There is very simple Express router with handler:

router.get('/users/:userId/roles/:roleId', function(req, res, next){
    const roleId = req.params.roleId;
    res.rest.resource = UserModel.findOne({ _id: req.params.userId}).exec().then(function(usr) {
        console.log(req.params.roleId); // => undefined
        console.log(roleId);            // => okay here
        const result = usr.roles.find( role => String(role._id) === String(roleId));
        return result;
    });
    next();
});

As it seen accessing req.params.roleId within promise returnes undefined. This is true only for cases when next() called outside promise's then.

I'm okay with asyncing and promises and understand that next() will be called before handler in then. But what happening with req.params.roleId? Why and where it mutates? Does middleware called by next() gets same but mutated req?

Note: res.rest.resource used by middleware called later to build right REST-like response.

like image 669
Alex Povar Avatar asked Feb 09 '16 16:02

Alex Povar


1 Answers

The code as it is is kind of indeterministic in its execution.

Something mutates the role ID in the next() handler, and since it takes a while for findOne() to eventually dispatch to the then handler, that mutation has already happened.

Without knowing further details of your app, it looks like this might be the correct implementation.

router.get('/users/:userId/roles/:roleId', function(req, res, next) {
    const roleId = req.params.roleId;
    UserModel.findOne({ _id: req.params.userId}).exec().then((usr) => {
        const result = usr.roles.find(role => String(role._id) === String(roleId));
        res.rest.resource = result;
        next(); // <-- only dispatch to next after we find the resource result
    });
});

Edit:

I dug a little deeper. See this little example app:

var express = require('express');
var app = express();

app.use(function (req, res, next) {
    var v = 0 | +new Date();
    console.log("middleware 1 setting foos to ", v);
    req.params.foo = v;
    req.foo = v;
    next();
});

app.use(function (req, res, next) {
    console.log("middleware 2 reading foos and starting timer:", req.params.foo, req.foo);
    setTimeout(function() {
        console.log("middleware 2: foos are now", req.params.foo, req.foo);
    }, 1000);
    next();
});

app.get("/", function(req, res) {
    res.send("params = " + JSON.stringify(req.params) + " and foo = " + req.foo);
});

app.listen(3000);

The output for a request is

middleware 1 setting foos to  -902674369
middleware 2 reading foos and starting timer: undefined -902674369
middleware 2: foos are now undefined -902674369
middleware 1 setting foos to  -902673113
middleware 2 reading foos and starting timer: undefined -902673113
middleware 2: foos are now undefined -902673113

and the browser output is params = {} and foo = -902673113, so it turns out that you are not allowed to touch req.params, but you can add any other properties to the req object and they will travel along fine.

This seems to be because of the route matching layer rewriting params on each step.

like image 182
AKX Avatar answered Sep 19 '22 00:09

AKX