简体   繁体   中英

Express.js Cannot read property 'req' of undefined

My Express.js app has been throwing Cannot read property 'req' of undefined . In essence, it listens for a GET request, grab the content query, and then reply with a table. Here's the parts that present the problem.

index.js

var panels = require('./modules/panels.js');
app.get('/panel', function (req, res) {
    var user;
    if (user = req.session.user) {
        panels.getContents(req.query.content, user.innId, res.send);
    } else {
        res.sendStatus(401);
    }
});

modules/panels.js

exports.getContents = function(panelName, innId, callback) {
    var response = "";
    switch (panelName) {
        case 'tenants':
            con.query(queryString, queryParams, function(err, rows) {
                if (err) {
                    handle(err);
                } else {
                    if (rows.length == 0) {
                        var tenants = 0;
                        var debtors = 0;
                    } else {
                        var tenants = rows[0].tenants;
                        var debtors = rows[0].length;
                    }
                    response = convertToHTMLTable(rows);
                }
                callback(response); /*THE ERROR POINTS HERE */
            });
            break;

        /* other cases here */

        default:
            response += "Invalid Request";
            callback(response);
    }
}

What did I do wrong? My guess is that I'm not suppose to pass res.send as a callback. So, how can I fix it?

I also experienced this error, and after chewing on the error message for a bit and staring at my code for too long, It clicked.

I (and the asker above) had code that looked like this:

  somethingAsync
  .then(res.send) // <-- storing send() divorced from parent object "res"
}

The problem is that when res.send gets called later, it is divorced from its dynamic scope--which means that calls to this inside of the send method, which would normally refer to res , now refer to global . Evidently, res.send contains a reference to this.req , given the error message.

An equivalent scenario:

res = {
    send: function() { 
        console.log(this.originalMessage.req);
    },
    originalMessage: { req: "hello SO" },
};

res.send();
// # "hello SO"
const x = {};
x.send = res.send;
x.send();
// # Uncaught TypeError: Cannot read property 'req' of undefined

Or, to put it another way:

new Promise(_ => _())
.then(_ => console.log(this.msg.req));
// # Uncaught (in promise) TypeError: Cannot read property 'req' of undefined

Two solutions:

handler(req, res) {
  somethingAsync
  .then(() => res.send())
   // ^ send will be called as a method of res, preserving dynamic scope
   // that is, we're storing a function that, when called, calls
   // "res.send()", instead of storing the "send" method of "res" by itself.
}

And a theoretically more idiomatic approach is

handler(req, res) {
  somethingAsync
  .then(res.send.bind(res))
  //^ res is explicitly bound to the saved function as its dynamic scope
}

Pernicious little gotcha of dynamic scoping, that one. Seems like express should ditch the dynamic scoping there, or at least throw a better error...

try this in index.js

panels.getContents(req.query.content, user.innId, res);

and into panels.js

exports.getContents = function(panelName, innId, response) {
    var response = "";
    switch (panelName) {
        case 'tenants':
            con.query(queryString, queryParams, function(err, rows) {
                if (err) {
                    handle(err);
                } else {
                    if (rows.length == 0) {
                        var tenants = 0;
                        var debtors = 0;
                    } else {
                        var tenants = rows[0].tenants;
                        var debtors = rows[0].length;
                    }
                    response.send(convertToHTMLTable(rows));
                }
            });
            break;

        /* other cases here */

        default:
            error += "Invalid Request";
            response.send(error)
    }
}

When you passed res.send as an argument to the getContents function ( panels.getContents(req.query.content, user.innId, res.send); ) it is passed in a new variable and you called it callback .

So res.send is now stripped from it's original/supposed-to-be lexical context / binding which is the res object in this case, and it appears that res.send uses the req property in the res object using this.req .

What you've done here is unintentionally changing the value of this for the function send to be the global object.

Solution

there are two ways to fix this problem

  • panels.getContents(req.query.content, user.innId, () => res.send);

// OR

  • panels.getContents(req.query.content, user.innId, res.send.bind(res));

in the second solution, you bind the function res.send to always treat any value of this to be the res object ( bind it to the res object )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM