Why is 'this' undefined inside class method when using promises? [duplicate] - javascript

This question already has answers here:
setTimeout and "this" in JavaScript
(5 answers)
Closed 7 years ago.
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?

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() })

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.

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() then myObj is going to be the this context.**
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.

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.

Related

ERROR TypeError: this.setNormal is not a function - Angular

I am trying to call a function in inside a function by using this keyword. But I am getting an error like
ERROR TypeError: this.setNormal is not a function
So as per my understanding the way I am calling function inside might be cause of the error. Please correct me the approach.
// first function
fun1 (){
setTimeout(function() {
this.setNormal();
}, 2000);
}
// second function
setNormal(){
}
The problem with:
// first function
fun1 (){
setTimeout(function() {
this.setNormal();
}, 2000);
}
// second function
setNormal(){
}
Is the way you've defined this since you are using regular function(){} syntax. If you read the specification for setTimeout
Code executed by setTimeout() is called from an execution context
separate from the function from which setTimeout was called. The usual
rules for setting the this keyword for the called function apply, and
if you have not set this in the call or with bind, it will default to
the global (or window) object in non–strict mode, or be undefined in
strict mode. It will not be the same as the this value for the
function that called setTimeout.
I am assuming that you aren't defining setNormal in the global scope...hence the problem.
Now, in ES6, lot's of people use () => {} function syntax to close over the this in the enclosing scope.
An arrow function does not have its own this. The this value of the
enclosing lexical context is used i.e. Arrow functions follow the
normal variable lookup rules. So while searching for this which is not
present in current scope they end up finding this from its enclosing
scope.
Change
setTimeout(function() {
to
setTimeout(() => {
the arrow function will allow you to use this inside setTimeout callback function (the this will point to object outside this function).

How to scope a callback function to be called from within a block?

I'm using a third-party API which requires a callback function called callback_data(json) when the response comes back.
However, it seems to be calling callback_data(json) at the global scope. I would like to scope the callback_data(json) function within a block so that I can control the flow through promise.
Here's my attempt to do this:
return new Promise(resolve => {
(function(callback_data) {
api.request({"q": term}); //The third-party API call
})(function(json) {
resolve({...json.data, q: term})
});
}
);
So, the line api.request({"q": term}); will call callback_data(json) function when it receives the response from the server. But it calls the callback function at the global scope. I want it to call from within the block the api.request() was initiated.
What I did was I thought I could put everything into a self invoking function and pass in the callback_data(json) function as a parameter into the self invoking function. I thought this would scope everything within that self invoking function block. Unfortunately, this didn't work.
How can I do scope a callback function, which supposed to be at the global scope, to be called from within a block?
I'm using Typescript in my code.
If the API is really coded such that it issues:
callback_data(json);
...from within a scope you can't add functions to, there's nothing you can do. The API is badly designed (unless it's JSONP, in which case that's the only way it can work, but it should let you specify the name of the function) and callback_data must be global.
That doesn't mean that it has to be long-lived. You can declare it globally:
var callback_data = null; // At global scope
...and then in your code, assign a value to it and clear that value when done:
return new Promise(resolve => {
callback_data = function() { // Assign
callback_data = null; // Clear on callback
resolve({...json.data,
q: term
});
};
api.request({"q": term});
});
If it's JSONP and it lets you specify the callback name in the request, that would be better as you can create a different callback name for each request:
// Using `window` as JSONP is by definition in a browser
window.lastCallbackId = 0;
return new Promise(resolve => {
++lastCallbackId;
var name = "myCallback" + lastCallbackId;
window[name] = function() { // Assign, will be "myCallback1", "myCallback2", ...
window[name] = null; // Clear on callback
resolve({...json.data,
q: term
});
};
api.request({
"q": term,
callbackName: name // Note passing the name to it
});
});

Object method with ES6 / Bluebird promises

I am using node v0.11.14-nightly-20140819-pre on Windows with harmony flag.
I have JavaScript object with two methods defined in its prototype:
function User (args) {
this.service= new Service(args);
}
User.prototype.method2 = function (response) {
console.log(this); // <= UNDEFINED!!!!
};
User.prototype.method1 = function () {
.............
this.service.serviceMethod(args)
.then(this.method2)
.catch(onRejected);
};
function onRejected(val) {
console.log(val);
}
serviceMethod of Service object returns a promise.
When I use User object like below:
let user = new User(args);
user.method1();
this in method2 of object User ends up undefined when called by then once promise is fulfilled.
I tried using both ES6 and Bluebird promise implementation.
Why this ends up being undefined in this case?
Why this ends up being undefined in this case?
Because you're passing a function, not a method-bound-to-an-instance. This problem is not even promise-specific, see How to access the correct `this` context inside a callback? for the generic solution:
….then(this.method2.bind(this))… // ES5 .bind() Function method
….then((r) => this.method2(r))… // ES6 arrow function
However, Bluebird does offer an other way to call the function as a method:
this.service.serviceMethod(args)
.bind(this)
.then(this.method2)
.catch(onRejected);
I should add that this is a generic Javascript issue and can also be solved using plain javascript features. For example, you could also do this:
User.prototype.method1 = function () {
.............
this.service.serviceMethod(args)
.then(this.method2.bind(this))
.catch(onRejected);
};
This uses Function.prototype.bind() which is built into Javascript and present on every function. This creates a function stub (which is what is passed to .then() and that stub will automatically reattach the desired this value before calling method2().

JavaScript and 'this' inside immediately executed functions

I have been reading up on the intricacies of 'this' in JavaScript. Given this code:
$(document).ready(function() {
console.dir(this);
(function foo() {
console.dir(this);
})();
});
In Chrome, the console shows the first 'this' as an 'HTMLDocument' (as I expected), but the second 'this' is 'undefined'. Can someone point me to a good explanation of why?
They way in which a javascript function is invoked changes the meaning of this inside of it. Here you've invoked a method which isn't associated with any object hence it has no this value to bind to.
In the first case the callback is being invoked via JQuery and they are manipulating the callback such that this points to the document DOM element. It's easy to visualize this as your callback being invoked with apply
yourCallback.apply(document, null);
You can fix the second version like so
$(document).ready(function() {
console.dir(this);
var that = this;
(function foo() {
console.dir(that);
})();
});
Or another way using apply
$(document).ready(function() {
console.dir(this);
(function foo() {
console.dir(this);
}).apply(this, null);
});
this is the context with which the current function has been called.
When calling a function with the object.method() syntax, object become the context.
When calling a function with the function() syntax, there is no context: it's null.
jQuery calls your ready function with document as the context. And you call your immediately executed function without context, which is why it's null.
You can do this:
// assign `this` to that so you can access `this` through `that`
var that = this;
(function foo() {
console.log(that);
}());
Or this:
// bind the function to `this` and then call it
$.proxy(function() {
console.log(this);
}, this)();
See jQuery.proxy.
Or even this:
// call the function with `this` as context
(function() {
console.log(this);
}).call(this);
See Function.call.
If that code is put into the console of a page with jQuery loaded, the second "this" is DOMWindow, which is the global "this". It is not undefined.
There must be something else on your page.
The second this is in an anonymous function which has no context. this is always an implicit variable at the function level, so your inner functions this is shadowing the outer this

jQuery, Javascript, Object Notation - Explanation of Differences in Callback Behavior

Consider the following jQuery implementation defined using an object literal...
$(function() {
var myObject = {
methodOne: function()
{
$('#element').animate(
{'marginLeft': '50px'},
'slow',
function() {
myObject.methodTwo();
}
);
},
methodTwo: function()
{
$('#element').animate(
{'marginLeft': '-50px'},
'slow',
function() {
myObject.methodOne();
}
);
}
} // End myObject
myObject.methodOne(); // Execute
});
For the record, the above code works just as expected. What I don't understand is why a subtle and seemingly harmless change like the following...
methodOne: function()
{
$('#element').animate(
{'marginLeft': '50px'},
'slow',
myObject.methodTwo() // No more anonymous function
);
},
... to both methodOne and methodTwo causes a browser error stating too much recursion. What's the difference between how I've declared my callback? Also, if I bring back the anonymous function declaration, but modify the object reference to look like this...
methodOne: function()
{
$('#element').animate(
{'marginLeft': '50px'},
'slow',
function() {
this.methodTwo(); // assuming 'this' refers to 'myObject'
}
);
},
... I get one good pass through methodOne and upon callback my browser freaks out because it cannot find methodTwo. My guess is that I fell out of scope somewhere, but I can't rightly decide where. Your insight is much appreciated!
Lets break this down
$('#element').animate(
{'marginLeft': '50px'},
'slow',
function() {
myObject.methodTwo();
}
);
In this example you pass a function object as a callback. That function object is called when animation is done. It has the myObject object shared via closure, so it can easily find it and call a method on it. Awesome!
$('#element').animate(
{'marginLeft': '50px'},
'slow',
myObject.methodTwo() // No more anonymous function
);
Here something different is going on. As the callback here you are actually passing the return value of myObject.methodTwo(), and not the actual function object. So since methodTwo() doesn't return anything, then undefined is actually passed as your callback. Meaning that the animate() function thinks there is no callback.
So maybe you meant to try this!
$('#element').animate(
{'marginLeft': '50px'},
'slow',
myObject.methodTwo // No more anonymous function or invocation
);
Well this still wouldn't work. Now you are passing a function object for the callback, yes, but it will lose context (this). It turns out that when you invoke a function object on it's own, then this is the global object. Check this out:
var obj = {
foo: function() { console.log(this) }
};
obj.foo() // logs obj
var func = obj.foo;
func() // logs window (the global object in a browser)
So you cant pass a function object directly in for a callback that is supposed to be invoked like a method with an object as the receiver. Internally to the animate() method there, it executes callback() for you, which is a call that does not preserve the value of this for you at all.
So why didnt this one work?
$('#element').animate(
{'marginLeft': '50px'},
'slow',
function() {
this.methodTwo(); // assuming 'this' refers to 'myObject'
}
);
When an anonymous function is invoke as the callback, just like breaking off a method, this defaults to the window object. So this code actually calls window.methodTwo(), which doesn't exist, and it explodes.
So the accepted standard JS way to do this your first way.
someFunc(arg1, arg2, function() {
someObj.someMethod()
});
That should always work, even though it seems wasted because you invoking 2 function to do one thing. But as you are discovering it's the least error prone.
Learning how this works in JS is a painful experience, but when you get it, you find that the rules are pretty straight forward and easy to manipulate.
If you still dont like that, you can do some tricky js magic. Like underscore.js bind() method.
someFunc(arg1, arg2, _.bind(someObj.someMethod, someObj));
This returns a function that will always run with someObj as this which can safely be used as a callback.
Or if you want to try out CoffeeScript it has a fat arrow to preserve context, wich compiles to JS similar to what the underscore.js bind() method does.
someObj =
foo: ->
someFunc arg1, arg2, =>
this.bar()
bar: ->
alert 'got the callback!'
someObj.foo()
In this case, the => style of function declaration preserves the context of the scope where it appears, so this can be safely used.
when you give jquery the function to execute you are actually passing in a function pointer and not what you want to execute.. it basically does exec on the function reference that you passed and thus when you changed it to obj.method() it longer understood it..
The problem with the myObject.methodTwo() is that myObject.methodTwo() is evaluated before being passed as a function. Javascript and by extension jQuery expects a function pointer in cases like these. I.E. setTimeout function.
In the below usage, an anonymous function is created (just a block of statements) and is registered as the callback function. Note the use of 'function(){'. The anonymous function does exactly one thing: calls myObject.methodOne()
function() {
myObject.methodOne();
}
regarding you second question in the callback function the this refers to a different object and not your original object anymore and that is why that failed. if you want to pass your this reference to the callback function read this: http://thomasdavis.github.com/tutorial/anonymous-functions.html
When you put the callback as myObject.methodTwo(), you are actually invoking methodTwo then and there. Then inside methodTwo it does the same thing by calling methodOne...which calls methodTwo, which calls methodOne, and so on. So you have an infinite recursion.
When you wrap that same line of code inside an anonymous function, it does not invoke the function immediately. You are now passing the function itself, not the returned value. The function using the callback then determines when (if ever) to actually invoke the function.
As zzzz and Paul stated, you're calling the function and passing its return value, rather than passing a callback. But you don't need to wrap that in an anonymous function to make it work - simply remove the () after the method and it'll work fine.
This.methodTwo here is looking for local reference, This belong to current scope, so method two is define out side the current scope you can use . I just quick view the code and find the following suggestion.
methodOne: function()
{
var that = this;
$('#element').animate(
{'marginLeft': '50px'},
'slow',
function() {
that.methodTwo(); // assuming 'this' refers to 'myObject'
}
);
}

Categories

Resources