admin管理员组文章数量:1194114
Why does Promise.then
passes execution context of undefined
when using a class method as callback, and window
when using a "normal function"?
Is the class method detached from its owning object/class? and why undefined
and not window
?
function normal() {
console.log('normal function', this);
}
const arrow = () => {
console.log('arrow function', this);
}
function strictFunction() {
'use strict';
console.log('strict function', this);
}
class Foo {
test() {
this.method(); // Foo
Promise.resolve().then(() => console.log('inline arrow function', this)); // Foo
Promise.resolve().then(normal); // window
Promise.resolve().then(arrow); // window
Promise.resolve().then(strictFunction); // undefined
Promise.resolve().then(this.method); // undefined <-- why?
}
method() {
console.log('method', this);
}
}
const F = new Foo();
F.test();
(jsFiddle)
I would expect the context of this.method
to be lost but cannot understand why the different behavior between this.method
and "normal" and arrow functions.
Is there a spec for this behavior? The only reference I found was Promises A+ refering to that "in strict mode this
will be undefined
inside; in sloppy mode, it will be the global object
.".
Why does Promise.then
passes execution context of undefined
when using a class method as callback, and window
when using a "normal function"?
Is the class method detached from its owning object/class? and why undefined
and not window
?
function normal() {
console.log('normal function', this);
}
const arrow = () => {
console.log('arrow function', this);
}
function strictFunction() {
'use strict';
console.log('strict function', this);
}
class Foo {
test() {
this.method(); // Foo
Promise.resolve().then(() => console.log('inline arrow function', this)); // Foo
Promise.resolve().then(normal); // window
Promise.resolve().then(arrow); // window
Promise.resolve().then(strictFunction); // undefined
Promise.resolve().then(this.method); // undefined <-- why?
}
method() {
console.log('method', this);
}
}
const F = new Foo();
F.test();
(jsFiddle)
I would expect the context of this.method
to be lost but cannot understand why the different behavior between this.method
and "normal" and arrow functions.
Is there a spec for this behavior? The only reference I found was Promises A+ refering to that "in strict mode this
will be undefined
inside; in sloppy mode, it will be the global object
.".
4 Answers
Reset to default 13The quote you have there tells you why:
in strict mode
this
will be undefined inside; in sloppy mode, it will be the global object.
The ES6 spec says that:
All parts of a ClassDeclaration or a ClassExpression are strict mode code
Therefore, because of strict mode, this
within an unbound class method, will be undefined
.
class A {
method() {
console.log(this);
}
}
const a = new A();
a.method(); // A
const unboundMethod = a.method;
unboundMethod(); // undefined
This is the same behavior you would get if you passed a normal function with strict mode because this
binding is undefined
by default in strict mode, not set to the global object.
The reason normal
and arrow
have this
as window
is because they are not within the class and thus not wrapped in strict mode.
As far as promises and the then
method, it will just pass undefined
as this
but won't override already bound this
.
If you look at the PromiseReactionJob spec:
The job PromiseReactionJob with parameters reaction and argument applies the appropriate handler to the incoming value, and uses the handler's return value to resolve or reject the derived promise associated with that handle.
...
let handlerResult be Call(handler, undefined, «argument»).
The second argument to Call is the this
value, which is set to undefined
.
This has nothing to do with Promises, but rather the context in which this
is called.
Case 1:
this.method(); // Foo
Here method
is a function defined within the Foo
class, so this
is evaluated as the object that triggered the function, which is this
in this.method
. Hence - Foo
is displayed.
Case 2:
Promise.resolve().then(() => console.log('inline arrow function', this)); // Foo
Arrow functions are a feature of ES6, whose unique property is that the this
context in the enclosing context where it is defined. The function was called in a context where this === Foo
so that's what's displayed.
Case 3:
Promise.resolve().then(normal); // window
Promise.resolve().then(arrow); // window
The arrow function retains its context as the window since it's an arrow function, and the normal function is evaluated without a context, in which this
is evaluated to window when not in strict mode
.
Case 4:
Promise.resolve().then(strictFunction); // undefined
Since strict mode
is requested within the body of this function, which is declared on the window, this
is evaluated to undefined.
Case 5:
Promise.resolve().then(this.method); // undefined <-- why?
In this spec, it is defined that all Class code is strict code:
All parts of a ClassDeclaration or a ClassExpression are strict mode code.
In my case it helped the simple solution of defining "self".
app.component.ts
export class AppComponent implements OnInit {
public cards: Card[] = [];
public events: any[] = [];
constructor(private fbService: FacebookService) {
this.fbService.loadSdk();
}
ngOnInit() {
const self = this;
this.fbService.getEvents().then((json: any) => {
for (const event of json.data)
{
self.cards.push(
new Card({
imageUrl: 'assets/ny.jpg',
id: event.id,
name: event.name
}),
);
}
});
}
}
fb.service.ts
import { BehaviorSubject } from 'rxjs/Rx';
import { Injectable, NgZone } from '@angular/core';
import { Http } from '@angular/http';
declare var window: any;
declare var FB: any;
@Injectable()
export class FacebookService {
events: any[];
public ready = new BehaviorSubject<boolean>(false);
constructor(private zone: NgZone) {
}
public loadSdk() {
this.loadAsync(() => { });
}
public loadAsync(callback: () => void) {
window.fbAsyncInit = () => this.zone.run(callback);
// Load the Facebook SDK asynchronously
const s = 'script';
const id = 'facebook-jssdk';
const fjs = document.getElementsByTagName(s)[0];
// tslint:disable-next-line:curly
if (document.getElementById(id)) return;
const js = document.createElement(s);
js.id = id;
js.src = 'http://connect.facebook.net/en_US/all.js';
fjs.parentNode.insertBefore(js, fjs);
}
public getEvents(): Promise<any> {
return new Promise((resolve, reject) => {
FB.init({
appId: 'app_id',
xfbml: true,
status: true,
cookie: true,
version: 'v2.10'
});
FB.api(
'/salsaparty.bg/events',
'GET',
{
access_token: 'acess_token'
},
function (response) {
resolve(response);
}
);
});
}
}
The reason this.method
is undefined is because when you use it like that, you are actually just taking the function, devoid of context, as the callback. So, when it's run, it doesn't know this.
If you want to maintain context, use the bind
function.
Promise.resolve().then(this.method.bind(this))
Bind will bind the context to the method. It's essentially equivalent to this:
Promise.resolve().then(((self) => () => self.method())(this))
which is a wrapper to map the context to a variable in a scope.
With a class method, when you get it as a variable, it's essentially no difference from a variable containing a reference to a function.
For example:
const a = () => {};
class Foo {
a() {}
}
const foo = new Foo();
console.log(a); // just a function
console.log(foo.a) // just a function
console.log(foo.a()) // a function called with a context of foo
When you call a method on an object, like foo.a()
it's essentially the same as doing foo.a.call(foo)
, where you set the context of a
to foo. When you just take foo.a
and separate it from foo
, it's the same as doing foo.a.call(window)
(or global
in Node).
Here is some code that illustrates the differences. You can also see how if you bind
it, it'll maintain context.
class Foo {
constructor() {
this.b = this.b.bind(this);
}
a () {
return this;
}
b () {
return this;
}
}
const foo = new Foo();
const a = foo.a;
const b = foo.b;
const bound = foo.a.bind(foo);
console.log('A', foo.a().constructor.name);
console.log('A', a());
console.log('A', a.apply(foo).constructor.name);
console.log('A', bound().constructor.name);
console.log('B', foo.b().constructor.name);
console.log('B', b().constructor.name);
本文标签: javascriptPromisethen execution context when using class methods as callbackStack Overflow
版权声明:本文标题:javascript - Promise.then execution context when using class methods as callback - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1738488429a2089561.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
obj.method
is always detached fromobj
- this isn't Promises specific. – Alnitak Commented Jan 18, 2017 at 18:06undefined
and notwindow
as expected (jsFiddle)? – Sergio Commented Jan 18, 2017 at 18:07class
methods are always strict mode.then
passes nothing (undefined
) as context, the rest is just the usual behavior of thethis
keyword. – Bergi Commented Jan 18, 2017 at 18:29