Angular 数据绑定不适用于 async/await,但它适用于 Promise

Posted

技术标签:

【中文标题】Angular 数据绑定不适用于 async/await,但它适用于 Promise【英文标题】:Angular data binding won't work with async/await, yet it will with promises 【发布时间】:2018-08-23 08:37:10 【问题描述】:

如果在 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')
  

为什么最后两个行为不一样?他们不应该是同一件事吗?

离子:3.9.2

角度: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()
  

【问题讨论】:

stackblitz.com/edit/angular-x8szh6 我觉得不错。 @Jeto 它适用于console.log,问题是如果您在具有数据绑定的组件中尝试它,则值不会更新 那你能用它构建一个最小的 StackBlitz 吗? 我在试,之前没听说过:) 我似乎无法在那里重现它...我的包是不同的版本,我也无法设置 tsconfig。 【参考方案1】:

这与变化检测在 Angular 中的工作方式有关。看: https://stackblitz.com/edit/angular-jajbza?file=app%2Fapp.component.ts

我打赌 Ionic 默认使用 OnPush 策略,或者您已启用它,就像我在 Blitz 中所做的那样。一切都很好,IMO 无论如何都应该默认启用它,因为它会迫使您考虑这些事情并编写更高性能的代码。

无法明确说明为什么在您调用 .then 时在上一个示例中更新了您的视图。也许在那种情况下,CD 设法跟踪 Promise,但不能使用异步函数。如果你不这样做,异步函数本身会返回一个 Promise,所以基本上在编译之后,async handle() 返回类似 Promise.resolve(null) 的东西,尽管实际的 JS 代码可能看起来比这更混乱。

编辑:另一种可能比手动调用 detectChanges 更干净的方法是运行任何改变 Angular 区域内视图的东西:

import  NgZone  from '@angular/core';
constructor (private zone:NgZone)

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

Edit2:有趣的是,zone.run() 实际上并没有改变任何东西。在闪电战中进行了测试。所以手动CD是唯一的方法。避免 async/await 并尝试坚持使用 Observables 和 NG 的异步管道的另一个原因。 :)

【讨论】:

现在我必须将我的 async/await 代码恢复为 Promise,以便不必做这些类型的事情。尽管这里的一切都完全正常:stackblitz.com/edit/ionic-g8d9fu,但由于某种原因它在我的环境中不起作用......我什至明确设置了 changeDetection:ChangeDetectionStrategy.Default。有什么方法可以让它在我的环境中工作? 我认为可能是转译问题 这与离子有关。我没有使用它,但听说它在使用默认策略时会导致类似 OnPush 的效果。 Electron 也发生在我身上,所以关于这些环境的某些事情有时会导致更改检测“不仅仅是工作”。如果我是你,我会在这些异步函数中添加 cdRef.markForCheck() 以使其快速运行。 =) .. markForCheck() 而不是 detectChanges() 即使它可能会导致更多的开销,因为如果你碰巧在视图被破坏后调用它,detectChanges 可能会导致“你试图做某事”组件在此期间已被销毁。所以更安全:) 有趣的是,我实际上在使用电子【参考方案2】:

检查您的浏览器版本及其支持的内容,async/await 是 ES2017 原生的。如果您的浏览器不支持,请使用 ES2016 作为您的目标。

我需要用于 Electron 的 ES2016。

【讨论】:

回答我自己的问题 - 虽然我不能接受它作为另外两天的答案......【参考方案3】:

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

问题在于原生async 函数是如何实现的。正如this question 所证实的那样,它们不仅仅围绕全局Promise,而是依赖于可能因浏览器而异的内部机制。

Zone.js 修补了Promise,但不可能修补当前引擎实现中async 函数使用的内部承诺(这里是open issue)。

通常(async () => )() instanceof Promise === true。对于 Zone.js,这是不正确的; async 函数返回本机 Promise 的实例,而 Promise global 是由 Zone.js 修补的区域感知承诺。

为了使原生 async 函数在 Angular 中工作,应该额外触发更改检测。这可以通过显式触发它(正如另一个答案已经暗示的那样)或使用任何区域感知 API 来完成。使用区域感知承诺包装 async 函数结果的助手可以解决问题:

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

Here 是一个在async 方法上使用@nativeAsync 装饰器来触发更改检测的示例:

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

Here 是相同的示例,它没有使用额外的措施来触发变更检测,并且预期不会按预期工作。

在不需要转译步骤的环境中坚持本机实现是有意义的。由于Angular应用程序应该以任何方式编译,因此可以通过从ES2017切换到ES2015ES2016TypeScripttarget来解决问题。

【讨论】:

您能否提供一个在 Angular 应用程序上工作的示例?这会很有帮助:)【参考方案4】:

就像 estus 说的,目前zone.js 不支持原生 async/await,所以你不能编译针对ES2017 的打字稿,我正在努力,https://github.com/angular/zone.js/pull/795,我做了一个可以在nodejs中运行的工作演示,但是在浏览器(chrome)中,它仍然需要一些时间,因为chrome现在不打开javascript版本的AsyncHooks和PromiseHooks。

【讨论】:

以上是关于Angular 数据绑定不适用于 async/await,但它适用于 Promise的主要内容,如果未能解决你的问题,请参考以下文章

表单控件不适用于 Angular Material Mat-select

带有@input属性的Angular 7双向绑定

淘汰赛中的数据绑定不适用于多个属性

Android 数据绑定不适用于 <merge> 属性

Angular:Mat Sort 不适用于类似扩展的表格

Angular4按过滤器分组不适用于管道:ngx-pipes