Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 880
starleaf1 Avatar asked Jan 23 '17 08:01

starleaf1


2 Answers

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...

like image 74
Kyle Baker Avatar answered Sep 28 '22 01:09

Kyle Baker


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)
    }
}
like image 25
Kaushik Makwana Avatar answered Sep 27 '22 23:09

Kaushik Makwana