web-dev-qa-db-fra.com

Pourquoi la méthode de classe interne "ceci" est-elle indéfinie lors de l'utilisation de promesses?

J'ai une classe javascript et chaque méthode renvoie une promesse Q. Je veux savoir pourquoi this est indéfini dans method2 et method3. Existe-t-il une manière plus correcte d'écrire ce code?

function MyClass(opts){
  this.options = opts;

  return this.method1()
    .then(this.method2)
    .then(this.method3);
}

MyClass.prototype.method1 = function(){
  // ...q stuff...

  console.log(this.options); // logs "opts" object

  return deferred.promise;
};

MyClass.prototype.method2 = function(method1resolve){
  // ...q stuff...

  console.log(this); // logs undefined

  return deferred.promise;
};

MyClass.prototype.method3 = function(method2resolve){
  // ...q stuff...

  console.log(this); // logs undefined

  return deferred.promise;
};

Je peux résoudre ce problème en utilisant bind:

function MyClass(opts){
  this.options = opts;

  return this.method1()
    .then(this.method2.bind(this))
    .then(this.method3.bind(this));
}

Mais pas tout à fait sûr pourquoi bind est nécessaire; Est-ce que .then() tuant this est éteint?

73
SteamDev

this est toujours l'objet sur lequel la méthode est appelée. Cependant, lorsque vous passez la méthode à then(), vous ne l'appelez pas! La méthode sera stockée quelque part et appelée plus tard. Si vous voulez conserver this, vous devrez le faire comme ceci:

.then(() => this.method2())

ou si vous devez le faire de manière antérieure à ES6, vous devez conserver this avant:

var that = this;
// ...
.then(function() { that.method2() })
107
lex82

Les gestionnaires de promesse sont appelés par défaut dans le contexte de l'objet global (window). En mode strict (use strict;), le contexte est undefined. C'est ce qui arrive à method2 et method3.

;(function(){
  'use strict'
  Promise.resolve('foo').then(function(){console.log(this)}); // undefined
}());

;(function(){
  Promise.resolve('foo').then(function(){console.log(this)}); // window
}());

Pour method1, vous appelez method1 en tant que this.method1(). Cette façon de l'appeler l'appelle dans le contexte de l'objet this qui est votre instance. C'est pourquoi le contexte à l'intérieur de method1 est l'instance.

18
Joseph

En gros, vous lui transmettez une référence de fonction sans référence au contexte. Le contexte this est déterminé de plusieurs manières:

  1. Implicitement. L'appel d'une fonction globale ou d'une fonction sans liaison suppose un contexte global. *
  2. Par référence directe. Si vous appelez myObj.f(), alors myObj sera le contexte this. **
  3. Reliure manuelle. Ceci est votre classe de fonctions telles que .bind et .apply. Celles-ci, vous indiquez explicitement ce qu'est le contexte this. Celles-ci ont toujours priorité sur les deux précédentes.

Dans votre exemple, vous passez une référence de fonction. Ainsi, lors de son invocation, il est implicite d'être une fonction globale ou sans contexte. Utiliser .bind résout ce problème en créant une nouvelle fonction dans laquelle this est explicitement défini.

* Ceci n'est vrai qu'en mode non strict. En mode strict, this est défini sur undefined.

** En supposant que la fonction que vous utilisez n'ait pas été liée manuellement.

2
Mike Cluck

Une façon dont les fonctions obtiennent leur contexte (this) provient de l'objet sur lequel elles sont appelées (c'est pourquoi method1 a le bon contexte - il est invoqué sur this Vous passez une référence à la fonction elle-même à then. Vous pouvez imaginer que l'implémentation de then ressemble à ceci:

function then( callback ) {

  // assume 'value' is the recently-fulfilled promise value
  callback(value);
}

Dans cet exemple, callback est une référence à votre fonction. Il n'a pas de contexte. Comme vous l'avez déjà noté, vous pouvez contourner ce problème en liant la fonction à un contexte avant de le transmettre à ce dernier.

0
James Allardice