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
切换到ES2015
或ES2016
TypeScripttarget
来解决问题。
【讨论】:
您能否提供一个在 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的主要内容,如果未能解决你的问题,请参考以下文章