Why is 'this' undefined inside class method when using promises?
Javascriptnode.jsPromiseThisQJavascript Problem Overview
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?
Javascript Solutions
Solution 1 - Javascript
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() })
Solution 2 - Javascript
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.
Solution 3 - Javascript
Basically, you're passing it a function reference with no context reference. The this
context is determined in a few ways:
- Implicitly. Calling a global function or a function without a binding assumes a global context.*
- By direct reference. If you call
myObj.f()
thenmyObj
is going to be thethis
context.** - Manual binding. This is your class of functions such as
.bind
and.apply
. These you explicitly state what thethis
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.
Solution 4 - Javascript
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.