Why is 'this' undefined inside class method when using promises? [duplicate]
Asked Answered
P

4

130

I have a javascript class, and each method returns a Q promise. I want to know why this is undefined in method2 and method3. Is there a more correct way to write this 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;
};

I can fix this by using bind:

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

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

But not entirely sure why bind is necessary; is .then() killing this off?

Pyorrhea answered 21/1, 2016 at 17:46 Comment(4)
When you use bind() it creates another function with exactly the scope you will pass by params. Although It answers just your last question, take a look at the Mozila's documentation: developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…Tactile
In 8 words, explain how the heck is this a duplicate of that? I just had this exact same question come up, which would not have been answered by that. I know that already, but I'm coming to terms with promises, ES6 Classes, and this.Blase
@Paulpro not so sure this should be marked as a duplicate of the setTimeout issue; as the problem arises in two completely different ways. People looking to resolve their this scoping issues in the context of promises are immediately being pointed to a more indirect question, where the accepted answer uses anti-patterns from 2009. 2 + 2 = 4 !== ((8+2)*6)/15 = 4Pyorrhea
IMO definitely shouldn't be marked as a duplicate especially to a question about timeouts. This question specifically about Promises and the answers are a god send. Thank you.Burmeister
F
183

this is always the object the method is called on. However, when passing the method to then(), you are not calling it! The method will be stored somewhere and called from there later. If you want to preserve this, you will have to do it like this:

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

or if you have to do it the pre-ES6 way, you need to preserve this before:

var that = this;
// ...
.then(function() { that.method2() })
Filum answered 21/1, 2016 at 17:51 Comment(4)
great answer - or pre-ES6 ".then(this.method2.bind(this))"Pfister
I used .then(data => this.method(data))Acima
FAR FAR easyer way: .then(( foo = this) => {... //then use foo instead of this [also translates to the pre-es6 version] Just make sure it's not overwridden!Clinkstone
underrated explanation, struggled with this quite a bit when setting up promises, now fixex. thanksLongship
T
25

Promise handlers are called in the context of the global object (window) by default. When in strict mode (use strict;), the context is undefined. This is what's happening to method2 and method3.

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

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

For method1, you're calling method1 as this.method1(). This way of calling it calls it in the context of the this object which is your instance. That's why the context inside method1 is the instance.

Tripodic answered 21/1, 2016 at 17:52 Comment(1)
Now this is an answer that helped me to understand it.Fenian
N
7

Basically, you're passing it a function reference with no context reference. The this context is determined in a few ways:

  1. Implicitly. Calling a global function or a function without a binding assumes a global context.*
  2. By direct reference. If you call myObj.f() then myObj is going to be the this context.**
  3. Manual binding. This is your class of functions such as .bind and .apply. These you explicitly state what the this context is. These always take precedence over the previous two.

In your example, you're passing a function reference, so at it's invocation it's implied to be a global function or one without context. Using .bind resolves this by creating a new function where this is explicitly set.

*This is only true in non-strict mode. In strict mode, this is set to undefined.

**Assuming the function you're using hasn't been manually bound.

Northumbria answered 21/1, 2016 at 17:53 Comment(0)
W
1

One way functions get their context (this) is from the object on which they are invoked (which is why method1 has the right context - it's invoked on this). You are passing a reference to the function itself to then. You can imagine that the implementation of then looks something like this:

function then( callback ) {

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

In that example callback is a reference to your function. It doesn't have any context. As you've already noted you can get around that by binding the function to a context before you pass it to then.

Wickliffe answered 21/1, 2016 at 17:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.