使用类方法作为回调时的 Promise.then 执行上下文
Posted
技术标签:
【中文标题】使用类方法作为回调时的 Promise.then 执行上下文【英文标题】:Promise.then execution context when using class methods as callback 【发布时间】:2017-06-03 05:32:11 【问题描述】:为什么Promise.then
在使用类方法作为回调时传递undefined
的执行上下文,而在使用“普通函数”时传递window
?
类方法是否与其所属的对象/类分离?为什么是undefined
而不是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)
我预计this.method
的上下文会丢失,但无法理解为什么this.method
与“正常”和箭头函数之间的行为不同。
这种行为有规范吗?我发现的唯一参考是 Promises A+,它指的是 “在严格模式下 this
内部将是 undefined
;在草率模式下,它将是 global object
。”。
【问题讨论】:
作为obj.method
传递的类方法引用总是与obj
分离——这不是Promises 特定的。
@Alnitak 但为什么是undefined
而不是window
as expected (jsFiddle)?
不确定 - 也许 ES6 类方法是隐式严格的?
class
methods are always strict mode。 then
不传递任何内容 (undefined
) 作为上下文,其余的只是 usual behavior of the this
keyword。
【参考方案1】:
你那里的报价告诉你原因:
在严格模式下,
this
内部将是未定义的;在草率模式下,它将是全局对象。
ES6 spec 说:
ClassDeclaration 或 ClassExpression 的所有部分都是严格模式代码
因此,由于严格模式,未绑定类方法中的this
将是undefined
。
class A
method()
console.log(this);
const a = new A();
a.method(); // A
const unboundMethod = a.method;
unboundMethod(); // undefined
这与您在严格模式下传递普通函数的行为相同,因为this
绑定在严格模式下默认为undefined
,而不是设置为全局对象。
normal
和 arrow
具有 this
和 window
的原因是因为它们不在类中,因此未包装在严格模式中。
就承诺和then
方法而言,它只会将undefined
作为this
传递,但不会覆盖已经绑定的this
。
如果您查看PromiseReactionJob 规范:
带有参数 react 和 argument 的作业 PromiseReactionJob 将适当的处理程序应用于传入值,并使用处理程序的返回值来解析或拒绝与该句柄关联的派生承诺。
...
let handlerResult be Call(handler, undefined, «argument»).
Call 的第二个参数是 this
值,设置为 undefined
。
【讨论】:
【参考方案2】:这与 Promises 无关,而是调用 this
的上下文。
案例 1:
this.method(); // Foo
这里method
是Foo
类中定义的函数,所以this
被评估为触发函数的对象,即this.method
中的this
。因此 - Foo
会显示出来。
案例 2:
Promise.resolve().then(() => console.log('inline arrow function', this)); // Foo
箭头函数是 ES6 的一个特性,其独特的属性是 this
上下文在定义它的封闭上下文中。该函数是在 this === Foo
的上下文中调用的,所以这就是显示的内容。
案例 3:
Promise.resolve().then(normal); // window
Promise.resolve().then(arrow); // window
箭头函数将其上下文保留为窗口,因为它是一个箭头函数,并且普通函数在没有上下文的情况下进行评估,其中this
不在strict mode
中时被评估为窗口。
案例 4:
Promise.resolve().then(strictFunction); // undefined
由于在窗口上声明的此函数的主体内请求了strict mode
,因此this
被评估为未定义。
案例 5:
Promise.resolve().then(this.method); // undefined <-- why?
在这个spec中,定义了所有Class代码都是严格代码:
ClassDeclaration 或 ClassExpression 的所有部分都是严格模式代码。
【讨论】:
除非我有误解,否则不清楚 OP 的代码是否是模块的一部分。 @nem035 正确,我想错了。已编辑,谢谢。 "在窗口上定义的函数" - 不,这不是normal
得到window
的原因
那么处理这个问题的惯用语是什么?我希望在 Promise 的 then 中可以访问 React 类的外部范围。我可以创建 const that = this,但想知道是否有一些不那么混乱的东西。
@zero_cool 听起来你既可以在promise.then
回调中使用箭头函数,也可以在构造函数中创建一个回调并将其绑定到this
。【参考方案3】:
就我而言,它有助于定义“自我”的简单解决方案。
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);
);
);
【讨论】:
【参考方案4】:this.method
未定义的原因是因为当你这样使用它时,你实际上只是将没有上下文的函数作为回调。所以,当它运行时,它并不知道这一点。
如果要维护上下文,请使用bind
函数。
Promise.resolve().then(this.method.bind(this))
Bind 会将上下文绑定到方法。它本质上等同于:
Promise.resolve().then(((self) => () => self.method())(this))
这是一个将上下文映射到作用域中的变量的包装器。
对于一个类方法,当你把它作为一个变量来获取时,它本质上与一个包含对函数的引用的变量没有区别。
例如:
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
当您在对象上调用方法时,例如foo.a()
,它本质上与执行foo.a.call(foo)
相同,您将a
的上下文设置为foo。当你只取foo.a
并将其与foo
分开时,它与foo.a.call(window)
(或Node 中的global
)相同。
下面是一些说明差异的代码。您还可以看到如果您 bind
它,它将如何保持上下文。
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);
【讨论】:
你能解释更多“没有上下文”吗?为什么这与命名函数不同?以上是关于使用类方法作为回调时的 Promise.then 执行上下文的主要内容,如果未能解决你的问题,请参考以下文章