Angular`ngOnInit`中的异步/等待
Posted
技术标签:
【中文标题】Angular`ngOnInit`中的异步/等待【英文标题】:async/await in Angular `ngOnInit` 【发布时间】:2019-09-29 05:13:51 【问题描述】:我目前正在评估替换 Angular 的优缺点。 RxJS 的 Observable
和普通的 Promise
,这样我就可以使用 async
和 await
并获得更直观的代码风格。
我们的一个典型场景:在ngOnInit
中加载一些数据。使用Observables
,我们这样做:
ngOnInit ()
this.service.getData().subscribe(data =>
this.data = this.modifyMyData(data);
);
当我从getData()
返回Promise
并使用async
和await
时,它变为:
async ngOnInit ()
const data = await this.service.getData();
this.data = this.modifyMyData(data);
现在,很明显,Angular 不会“知道”ngOnInit
已经变成了async
。我觉得这不是问题:我的应用程序仍然像以前一样工作。但是当我查看OnInit
接口时,该函数显然没有以表明它可以声明为async
的方式声明:
ngOnInit(): void;
所以——底线:我在这里做的事情合理吗?或者我会遇到任何不可预见的问题?
【问题讨论】:
根据issue 17420中的this comment:“有人使用async ngOnInit
不是问题,这只是一种尴尬/不推荐的编码习惯。”
@ConnorsFan 在打开我的帖子之前,我实际上已经完全阅读了这个问题 :-)(应该链接它)。我仍然不确定,“尴尬”和“不推荐”是否有任何客观原因,还是 Angular 团队只是想推向响应式风格?
Here 是关于这个主题的另一本好书。
@qqilihq - 嗨,我正在考虑进行此转换。您是否继续进行并且对结果感到满意?有什么问题...?
@LeeGunn 这是马马虎虎。我到处使用它,但总的来说非常谨慎。原因(a)是错误处理(@Reactgular 在下面详细解释),(b)代码“嵌套”少一些(没有回调),但收益非常小,并且(c)我们正在使用代码库中有几个“真实”的 Observables(不断更新)——await
无济于事,我们最终会遇到两种不一致的方式(或者让新团队成员感到困惑)。
【参考方案1】:
这和你以前的没有什么不同。 ngOnInit
将返回一个 Promise,调用者将忽略该 Promise。这意味着调用者不会等待方法中的所有内容都完成后再继续。在这种特定情况下,这意味着视图将完成配置,并且可以在设置 this.data
之前启动视图。
这和你之前的情况一样。来电者不会等待您的订阅完成,并且可能会在填充 this.data
之前启动应用程序。如果您的视图依赖于data
,那么您可能有某种ngIf
设置来阻止您访问它。
只要您了解其中的含义,我个人并不认为这是一种尴尬或不好的做法。但是,ngIf
可能很乏味(无论哪种方式都需要它们)。我个人已经开始使用有意义的路由解析器,这样我就可以避免这种情况。数据在路线完成导航之前加载,我可以在加载视图之前知道数据可用。
【讨论】:
感谢您提及路由解析器。我认为这是解决这个经常发生的问题的更好方法。 "在这种特定情况下,这意味着视图将完成配置,并且可以在设置this.data
之前启动视图。" - 这是我认为的关键,除非您考虑它,否则它并不明显,并且对于试图维护您的代码的未来开发人员来说可能更不明显。出于这个原因,我同意上面提到的评论,即这实际上是“尴尬”和“不好的做法”。【参考方案2】:
现在,很明显,Angular 不会“知道” ngOnInit 已经变成异步的。我觉得这不是问题:我的应用仍然可以正常运行。
从语义上讲,它可以正常编译并按预期运行,但编写async / wait
的便利是以错误处理为代价的,我认为应该避免这种情况。
让我们看看会发生什么。
当一个承诺被拒绝时会发生什么:
public ngOnInit()
const p = new Promise((resolver, reject) => reject(-1));
上面生成以下堆栈跟踪:
core.js:6014 ERROR Error: Uncaught (in promise): -1
at resolvePromise (zone-evergreen.js:797) [angular]
at :4200/polyfills.js:3942:17 [angular]
at new ZoneAwarePromise (zone-evergreen.js:876) [angular]
at ExampleComponent.ngOnInit (example.component.ts:44) [angular]
.....
我们可以清楚地看到未处理的错误是由ngOnInit
触发的,还可以看到哪个源代码文件找到了有问题的代码行。
当我们使用被拒绝的async/wait
时会发生什么:
public async ngOnInit()
const p = await new Promise((resolver, reject) => reject());
上面生成以下堆栈跟踪:
core.js:6014 ERROR Error: Uncaught (in promise):
at resolvePromise (zone-evergreen.js:797) [angular]
at :4200/polyfills.js:3942:17 [angular]
at rejected (tslib.es6.js:71) [angular]
at Object.onInvoke (core.js:39699) [angular]
at :4200/polyfills.js:4090:36 [angular]
at Object.onInvokeTask (core.js:39680) [angular]
at drainMicroTaskQueue (zone-evergreen.js:559) [<root>]
发生了什么?我们不知道,因为堆栈跟踪在组件之外。
不过,您可能会想使用 Promise 并避免使用 async / wait
。因此,让我们看看如果在 setTimeout()
之后拒绝 promise 会发生什么。
public ngOnInit()
const p = new Promise((resolver, reject) =>
setTimeout(() => reject(), 1000);
);
我们将得到以下堆栈跟踪:
core.js:6014 ERROR Error: Uncaught (in promise): [object Undefined]
at resolvePromise (zone-evergreen.js:797) [angular]
at :4200/polyfills.js:3942:17 [angular]
at :4200/app-module.js:21450:30 [angular]
at Object.onInvokeTask (core.js:39680) [angular]
at timer (zone-evergreen.js:2650) [<root>]
再一次,我们在这里失去了上下文,不知道去哪里修复错误。
Observables 遭受错误处理的相同副作用,但通常错误消息的质量更好。如果有人使用throwError(new Error())
,Error 对象将包含堆栈跟踪,如果您使用HttpModule
,Error 对象通常是 Http 响应 告诉你请求的对象。
所以这里的故事的寓意是:抓住你的错误,尽可能使用 observables,不要使用async ngOnInit()
,因为它会作为一个难以找到和修复的错误回来困扰你。
【讨论】:
很好的解释。 这里缺少的部分是当Observable
中发生错误时堆栈跟踪的外观会更好吗?
@Peter 大多数抛出错误的 observables 通常来自HttpClient
并且是异步的。因此,当抛出错误时,堆栈跟踪将来自您的源代码之外。您可以使用catchError()
操作符来处理错误,如果您只需要堆栈跟踪帮助,那么catchError(err => throwError(err))
将重新抛出错误并将您的源代码添加到调用堆栈中。【参考方案3】:
我想知道immediately invoked function expression 的缺点是什么:
ngOnInit ()
(async () =>
const data = await this.service.getData();
this.data = this.modifyMyData(data);
)();
这是我能想到的唯一方法,无需将 ngOnInit()
声明为 async
函数即可使其工作
【讨论】:
但这与拥有一个单独的异步函数并在 ngOnInit ngOnInit () this.fetchData(); 中调用它一样好 private async fetchData() const data = await this.service.getData(); this.data = this.modifyMyData(data); @ABajpai 是的,但是没有单独的async function
也没有创建ngOninit() async
,这似乎是用户要求的。
这和只运行promise而不等待它是一样的,它会工作,但不等待它完成【参考方案4】:
我在 ngOnInit() 中使用了 try catch:
async ngOnInit()
try
const user = await userService.getUser();
catch (error)
console.error(error);
然后你会得到一个更具描述性的错误,你可以找到错误所在
【讨论】:
【参考方案5】:你可以使用rxjs函数of
。
of(this.service.getData());
将承诺转换为可观察序列。
【讨论】:
谢谢。但我的目标是避免 Observable 支持await
语法(在过去,我对基于回调的语法产生了强烈的反感:-))。
@qqilihq 我同意,Promise
很丑,然后我们得到了await
和async
,我很高兴,现在我们有了Observable
,我们又回到了丑陋但比Promise
.【参考方案6】:
ngOnInit
does NOT wait 承诺完成。如果您想像这样使用 await,可以将其设为异步函数:
import take from 'rxjs/operators';
async ngOnInit(): Promise<any>
const data = await this.service.getData().pipe(take(1)).toPromise();
this.data = this.modifyMyData(data);
但是,如果您使用 ngOnInit
而不是构造函数来等待函数完成,那么您基本上是在做与此等效的操作:
import take from 'rxjs/operators';
constructor()
this.service.getData().pipe(take(1)).toPromise()
.then((data => ;
this.data = this.modifyMyData(data);
);
它将运行异步函数,但不会等待它完成。如果您注意到有时它会完成,有时它不会,这实际上取决于您的函数的时间。
利用this post的思路,基本可以跑到zone.js
之外了。 NgZone
不包括 scheduleMacroTask
,但 zone.js
已经导入到 angular 中。
解决方案
import isObservable, Observable from 'rxjs';
import take from 'rxjs/operators';
declare const Zone: any;
async waitFor(prom: Promise<any> | Observable<any>): Promise<any>
if (isObservable(prom))
prom = prom.pipe(take(1)).toPromise();
const macroTask = Zone.current
.scheduleMacroTask(
`WAITFOR-$Math.random()`,
() => ,
,
() =>
);
return prom.then((p: any) =>
macroTask.invoke();
return p;
);
我个人把这个函数放在了我的core.module.ts
,虽然你可以把它放在任何地方。
像这样使用它:
constructor(private cm: CoreModule)
const p = this.service.getData();
this.post = this.cm.waitFor(p);
您还可以检查 isBrowser 以保持可观察性,或等待结果。
相反,您也可以导入 angular-zen
并像在 this post 中一样使用它,尽管您导入的内容会超出您的需要。
J
【讨论】:
async ngOnInit(): Promise 对我有用。【参考方案7】:我会做一些不同的事情,你不显示你的 html 模板,但我假设你正在用数据做一些事情,即
<p> data.Name </p> <!-- or whatever -->
您不使用异步管道是否有原因?即
<p> (data$ | async).Name </p>
或
<p *ngIf="(data$ | async) as data"> data.name </p>
在你的ngOnInit
:
data$: Observable<any>; //change to your data structure
ngOnInit ()
this.data$ = this.service.getData().pipe(
map(data => this.modifyMyData(data))
);
【讨论】:
【参考方案8】:这个问题的正确答案是使用 Angular 的Resolver 特性。使用 Resolve,您的组件在您告诉它渲染之前不会被渲染。
来自文档:
类可以实现为数据提供者的接口。一个数据 提供者类可以与路由器一起使用来解析数据 导航。该接口定义了一个调用的 resolve() 方法 当导航开始时。路由器等待数据 在路由最终激活之前解决。
【讨论】:
解析器是一个需要考虑的概念,但它们不是灵丹妙药。在某些情况下,onInit
是可取的。 (例如,更少的代码分散,在目标视图中显示加载指示器,......)【参考方案9】:
说白了,你的所作所为是不合理。 如果您在导航到该页面之前有哪些可用数据,请在您的路线中使用解析器。
【讨论】:
这已经被建议过了。请在此处查看我的答案:***.com/a/69147601/388827以上是关于Angular`ngOnInit`中的异步/等待的主要内容,如果未能解决你的问题,请参考以下文章