Angular数据绑定无法与async/await一起使用,但可以与promises一起使用。

11
数据绑定在await语句后更改值时不会更新。
  handle() {
    this.message = 'Works'
  }

  async handle() {
    this.message = 'Works'
  }

  async handle() {
    await new Promise((resolve, reject) => {
      resolve()
    })
    this.message = 'Works'
  }

  async handle() {
    await new Promise((resolve, reject) => {
      setTimeout(() => resolve(), 3000)
    })
    this.message = 'Doesn\'t work'
  }

  handle() {
    new Promise((resolve, reject) => {
      setTimeout(() => resolve(), 3000)
    })
    .then(() => this.message = 'Works')
  }

为什么最后两个不表现相同?它们不应该是同一件事吗?
Ionic版本:3.9.2
Angular版本:5.0.3
TypeScript版本:2.4.2
编辑:我遇到了另一个问题,这可能对某些人有用。
在构造函数中更改绑定值的行为与ionViewDidLoad或ngOnInit不同!
  constructor(private zone: NgZone) {
    // This will cause the same problems, bindings not updating
    this.handle()
  }

  constructor(private zone: NgZone) {
    // Unless you do this...
    this.zone.run(() => {
      this.handle()
    })
  }

  ionViewDidLoad() {
    // But I think this is better/cleaner
    this.handle()
  }

https://stackblitz.com/edit/angular-x8szh6 看起来对我没问题。 - Jeto
我正在尝试,以前没有听说过 :) - ovg
我似乎无法在那里复现它...我的软件包版本不同,也无法设置tsconfig。 - ovg
我认为你的问题不在于Angular本身,请参见此处 https://stackblitz.com/edit/angular-s3eweu?file=app%2Fapp.component.html - CREM
@CryingFreeman 看起来是这样,因为它在 stackblitz 上可以运行,但当我在我的环境中编译并在同一浏览器中运行时,它就无法工作了。 - ovg
显示剩余3条评论
4个回答

14

Angular 依赖于 Zone.js 进行变更检测,而 Zone.js 通过修补每个可以提供异步行为的 API 来实现这一点。

问题在于如何实现原生的 async 函数。正如在 这个问题 中所确认的那样,它们不仅仅是围绕全局的 Promise 进行包装,而是依赖于内部机制,这些机制可能因浏览器而异。

Zone.js 可以修补 Promise,但是目前引擎实现中使用的内部 promise 是无法修补的(这里 有一个未解决的问题)。

通常情况下,(async () => {})() instanceof Promise === true。但在 Zone.js 中,这不是真的;async 函数返回一个原生的 Promise 实例,而全局的 Promise 则是被 Zone.js 修补过的 zone-aware promise。

为了使原生的 async 函数在 Angular 中正常工作,需要额外触发变更检测。这可以通过显式触发它(如另一个答案所建议的那样),或者使用任何 zone-aware 的 API 来完成。一个将 async 函数结果包装在 zone-aware promise 中的帮助程序就可以解决问题:

function nativeAsync(target, method, descriptor) {
  const originalMethod = target[method];
  descriptor.value = function () {
    return Promise.resolve(originalMethod.apply(this, arguments));
  }
}

这里有一个示例,它在async方法上使用@nativeAsync装饰器来触发变更检测:

  @nativeAsync
  async getFoo() {
    await new Promise(resolve => setTimeout(resolve, 100));
    this.foo = 'foo';
  }

这里 是一个相同的示例,没有使用额外的措施来触发变更检测,因此按预期不起作用。

在不需要转译步骤的环境中,最好坚持使用本地实现。由于 Angular 应用程序应该被编译,因此可以通过将 TypeScript targetES2017 切换到 ES2015ES2016 来解决问题。


请问您能否提供一个在Angular应用中运行的示例?这会非常有帮助 :) - jbarradas
1
@jbarradas已更新答案中的plunk,以适应RxJS 6中的重大更改。 - Estus Flask

6

就像estus所说,目前zone.js不支持原生的async/await,因此您无法编译针对ES2017的typescript代码。我正在努力解决这个问题。https://github.com/angular/zone.js/pull/795。我已经制作了一个可以在nodejs中运行的演示版本,但在浏览器(chrome)中,它仍需要一些时间,因为chrome目前不支持JavaScript版本的AsyncHooks和PromiseHooks。


2
检查您的浏览器版本及其支持的内容,async/await 是 ES2017 中的本地内容。如果您的浏览器不支持,请将目标设置为 ES2016。
我需要在 Electron 中使用 ES2016。

回答自己的问题 - 尽管我不能在另外两天内接受它作为答案... - ovg

1
这与Angular中变更检测的工作方式有关。请参见: https://stackblitz.com/edit/angular-jajbza?file=app%2Fapp.component.ts 我猜Ionic默认使用OnPush策略,或者您已经启用了它,就像我在Blitz中所做的一样。这很好,我认为它应该成为默认设置,因为它迫使您考虑这些问题并编写更高效的代码。
不能确定为什么在调用.then时会更新您最后一个示例中的视图。也许在那种情况下,CD设法跟踪Promise,但是对于异步函数则不是。如果您没有返回值,异步函数本身会返回一个Promise,因此在转译之后,handle()返回类似于Promise.resolve(null)的内容,尽管实际的JS代码可能看起来比这更混乱。
编辑:另一种方法,可能比手动调用detectChanges更清晰的方法是将任何更改视图的内容放在Angular的zone内运行:
import { NgZone } from '@angular/core';
constructor (private zone:NgZone){}

// after await somePromise :
this.zone.run(() => { 
    this.someProperty = 'Something changed';
});

编辑2:有趣的是,zone.run()实际上不会改变任何东西。在blitz中测试了一下。因此,手动CD是唯一的方法。这是避免使用async/await并尝试坚持使用Observables和NG的异步管道的又一个原因。 :)


现在我必须将我的async/await代码还原为promises,以便不必做这些类型的事情。虽然在这里一切都完全正常:https://stackblitz.com/edit/ionic-g8d9fu,但由于某种原因,在我的环境中它无法工作...我甚至明确设置了changeDetection: ChangeDetectionStrategy.Default。有没有办法让它在我的环境中工作? - ovg
我认为这可能是一个转译问题。 - ovg
这与Ionic有关。我没有使用过它,但听说在使用默认策略时会导致OnPush类似的效果。在Electron中也发生了这种情况,因此这些环境中的某些因素有时会导致变更检测“不起作用”。如果我是你,我会在这些异步函数中添加cdRef.markForCheck()以快速解决问题。=) - funkizer
使用markForCheck()而不是detectChanges(),即使它可能会导致更多的开销,因为如果你在组件被销毁后调用detectChanges(),它会导致“你试图在视图已被销毁后做某事”的错误。所以使用markForCheck()更安全 :) - funkizer
有趣的是,我实际上正在使用 Electron。 - ovg
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接