I've tried to search over what seems to be the entire internet, but I'm still vexed by a problem with a JS class I'm writing for a Micro Service (still in learning a bit).
So, I try to call a class method on an instantiated object, and according to my knowledge and my (faulty I presume) unit tests it should work.
Well, I'll start with the error I receive:
GET /api/users 500 2.863 ms - 2649
TypeError: Cannot read property 'repository' of undefined
at list (C:\Users\<user>\Documents\Programming\node\kaguwa-ngn\kaguwa-user-service\controllers\user-controller.js:20:9)
at Layer.handle [as handle_request] (C:\Users\<user>\Documents\Programming\node\kaguwa-ngn\kaguwa-user-service\node_modules\express\lib\router\layer.js:95:5)
at next (C:\Users\<user>\Documents\Programming\node\kaguwa-ngn\kaguwa-user-service\node_modules\express\lib\router\route.js:137:13)
(And a lot more).
The code calling code:
user-controller.js
'use strict';
var utils = require('./utils');
class UserController {
constructor(repository) {
this.repository = repository || {};
}
/**
*
* Lists all users.
*
* @param {object} req
* @param {object} res
*/
list(req, res) {
this.repository.list(function (err, users) {
if (err) return res.status(500).json(utils.createError(500));
if (Object.keys(users).length !== 0) {
res.json(users);
} else {
res.status(404).json(utils.createNotFound('user', true));
}
});
}
// more code
}
module.exports = UserController
Caller of controller
user-api.js
'use strict';
var express = require('express');
var UserController = require('../controllers/user-controller');
var router = express.Router();
module.exports = function (options) {
var userController = new UserController(options.repository);
router.get('/users', userController.list);
// Mode code
return router;
};
I really jave no idea on why this
undefined in UserController
.
Any help would be greatly appriciated.
When you do this:
router.get('/users', userController.list);
what gets passed to your router is just a reference to the .list
method. The userController instance gets lost. This is not unique to routers - this is a generic property of how things are passed in Javascript. To understand further, what you are essentially doing is this:
let list = userController.list;
// at this point the list variable has no connection at all to userController
router.get('/users', list);
And, in Javascript's strict
mode, when you call a regular function without any object reference such as calling list()
above, then this
will be undefined
inside the function. That is what is happening in your example. To fix it, you need to make sure that your method is called with a proper object reference as in userController.list(...)
so that the interpreter sets the this
value appropriately.
There are multiple ways to solve this problem:
Make your own function wrapper
router.get('/users', function(req, res) {
userController.list(req, res);
});
This works in any version of Javascript.
Using .bind()
to make a wrapper for you that calls it with the right object
router.get('/users', userController.list.bind(userController));
This works in ES5+ or with a .bind()
polyfill.
Use an ES6 arrow function shortcut
router.get('/users', (...args) => userController.list(...args));
This works in ES6+
Personally, I prefer the .bind()
implementation because I think it's just simpler and more declarative/clearer than any of the others and the ES6 "shortcut" isn't really shorter.