web-dev-qa-db-fra.com

Pourquoi "this" n'est-il pas défini dans cette méthode de classe?

J'ai essayé de rechercher ce qui semble être tout Internet, mais je suis toujours contrarié par un problème avec une classe JS que j'écris pour un Micro Service (toujours en apprentissage un peu).

Donc, j'essaie d'appeler une méthode de classe sur un objet instancié, et selon mes connaissances et mes tests unitaires (défectueux je suppose) cela devrait fonctionner.

Eh bien, je vais commencer par l'erreur que je reçois:

    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)

(Et beaucoup plus).

Le code appelant le 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

Appelant du contrôleur

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;
};

Je ne sais vraiment pas pourquoi this non défini dans UserController.

Toute aide serait grandement appréciée.

18
KarlGdawg

Lorsque vous procédez ainsi:

router.get('/users', userController.list);

ce qui est transmis à votre routeur n'est qu'une référence à la méthode .list. L'instance userController est perdue. Ce n'est pas unique aux routeurs - c'est une propriété générique de la façon dont les choses sont passées en Javascript. Pour mieux comprendre, ce que vous faites essentiellement est le suivant:

let list = userController.list; 
// at this point the list variable has no connection at all to userController
router.get('/users', list);

Et, en mode strict de Javascript, lorsque vous appelez une fonction régulière sans référence d'objet telle que l'appel de list() ci-dessus, alors this sera undefined à l'intérieur la fonction. C'est ce qui se passe dans votre exemple. Pour y remédier, vous devez vous assurer que votre méthode est appelée avec une référence d'objet appropriée comme dans userController.list(...) afin que l'interpréteur définisse la valeur this de manière appropriée.

Il existe plusieurs façons de résoudre ce problème:

Créez votre propre wrapper de fonction

router.get('/users', function(req, res)  {
    userController.list(req, res);
});

Cela fonctionne dans n'importe quelle version de Javascript.


en utilisant .bind() pour créer un wrapper pour vous qui l'appelle avec le bon objet

router.get('/users', userController.list.bind(userController));

Cela fonctionne dans ES5 + ou avec un polyfill .bind().


tilisez un raccourci de la fonction de flèche ES6

router.get('/users', (...args) => userController.list(...args));

Cela fonctionne dans ES6 +


Personnellement, je préfère l'implémentation de .bind() parce que je pense qu'elle est juste plus simple et plus déclarative/plus claire que les autres et le "raccourci" ES6 n'est pas vraiment plus court.

50
jfriend00

router.get() n'appellera pas votre classe comme vous le pensez. Vous lui donnez une référence à une fonction qu'il appellera dans le cadre du router.get ce qui signifie que ce ne sera pas dans le contexte de votre userController.

Vous pouvez résoudre ce problème en faisant:

router.get('/users', function(){userController.list(...arguments)});

En d'autres termes, ne pas utiliser express userController pour list, utiliser express une fermeture qui aura userController appeler list avec les arguments donnés .

3
zero298