[英]async/await in Angular `ngOnInit`
I'm currently evaluating the pros 'n' cons of replacing Angular's resp.我目前正在评估替换 Angular 的利弊。 RxJS'
Observable
with plain Promise
so that I can use async
and await
and get a more intuitive code style. RxJS 的
Observable
带有普通的Promise
,这样我就可以使用async
和await
并获得更直观的代码风格。
One of our typical scenarios: Load some data within ngOnInit
.我们的典型场景之一:在
ngOnInit
中加载一些数据。 Using Observables
, we do:使用
Observables
,我们可以:
ngOnInit () {
this.service.getData().subscribe(data => {
this.data = this.modifyMyData(data);
});
}
When I return a Promise
from getData()
instead, and use async
and await
, it becomes:当我从
getData()
返回Promise
并使用async
和await
时,它变为:
async ngOnInit () {
const data = await this.service.getData();
this.data = this.modifyMyData(data);
}
Now, obviously, Angular will not “know”, that ngOnInit
has become async
.现在,很明显,Angular 不会“知道”
ngOnInit
已经变成了async
。 I feel that this is not a problem: My app still works as before.我觉得这不是问题:我的应用程序仍然像以前一样工作。 But when I look at the
OnInit
interface, the function is obviously not declared in such a way which would suggest that it can be declared async
:但是当我查看
OnInit
接口时,该函数显然没有以表明它可以声明为async
的方式声明:
ngOnInit(): void;
So -- bottom line: Is it reasonable what I'm doing here?所以——底线:我在这里做的事情合理吗? Or will I run into any unforseen problems?
还是我会遇到任何不可预见的问题?
It is no different than what you had before.它和你以前的没有什么不同。
ngOnInit
will return a Promise and the caller will ignore that promise. ngOnInit
将返回一个 Promise 并且调用者将忽略该承诺。 This means that the caller will not wait for everything in your method to finish before it proceeds.这意味着调用者在继续之前不会等待方法中的所有内容完成。 In this specific case it means the view will finish being configured and the view may be launched before
this.data
is set.在这种特定情况下,这意味着视图将完成配置,并且视图可能会在设置
this.data
之前启动。
That is the same situation you had before.这和你之前遇到的情况一样。 The caller would not wait for your subscriptions to finish and would possibly launch the app before
this.data
had been populated.调用者不会等待您的订阅完成,并且可能会在填充
this.data
之前启动应用程序。 If your view is relying on data
then you likely have some kind of ngIf
setup to prevent you from accessing it.如果您的视图依赖于
data
那么您可能有某种ngIf
设置来阻止您访问它。
I personally don't see it as awkward or a bad practice as long as you're aware of the implications.我个人并不认为这是尴尬或不好的做法,只要你意识到其中的含义。 However, the
ngIf
can be tedious (they would be needed in either way).但是,
ngIf
可能很乏味(无论哪种方式都需要它们)。 I have personally moved to using route resolvers where it makes sense so I can avoid this situation.我个人已经转向在有意义的地方使用路由解析器,这样我就可以避免这种情况。 The data is loaded before the route finishes navigating and I can know the data is available before the view is ever loaded.
在路线完成导航之前加载数据,我可以在加载视图之前知道数据可用。
Now, obviously, Angular will not “know”, that ngOnInit has become async.
现在,很明显,Angular 不会“知道”ngOnInit 已经变成异步了。 I feel that this is not a problem: My app still works as before.
我觉得这不是问题:我的应用程序仍然像以前一样工作。
Semantically it will compile fine and run as expected, but the convenience of writing async / wait
comes at a cost of error handling, and I think it should be avoid.从语义上讲,它可以正常编译并按预期运行,但是编写
async / wait
的便利是以错误处理为代价的,我认为应该避免这种情况。
Let's look at what happens.让我们看看会发生什么。
What happens when a promise is rejected:当 promise 被拒绝时会发生什么:
public ngOnInit() {
const p = new Promise((resolver, reject) => reject(-1));
}
The above generates the following stack trace:以上生成了以下堆栈跟踪:
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]
.....
We can clearly see that the unhandled error was triggered by a ngOnInit
and also see which source code file to find the offending line of code.我们可以清楚地看到未处理的错误是由
ngOnInit
触发的,还可以看到哪个源代码文件可以找到有问题的代码行。
What happens when we use async/wait
that is reject:当我们使用拒绝的
async/wait
时会发生什么:
public async ngOnInit() {
const p = await new Promise((resolver, reject) => reject());
}
The above generates the following stack trace:以上生成了以下堆栈跟踪:
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>]
What happened?发生了什么? We have no clue, because the stack trace is outside of the component.
我们不知道,因为堆栈跟踪在组件之外。
Still, you might be tempted to use promises and just avoid using async / wait
.不过,您可能会倾向于使用 Promise 而只是避免使用
async / wait
。 So let's see what happens if a promise is rejected after a setTimeout()
.因此,让我们看看如果在
setTimeout()
之后拒绝 promise 会发生什么。
public ngOnInit() {
const p = new Promise((resolver, reject) => {
setTimeout(() => reject(), 1000);
});
}
We will get the following stack trace:我们将得到以下堆栈跟踪:
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>]
Again, we've lost context here and don't know where to go to fix the bug.同样,我们在这里失去了上下文,不知道去哪里修复错误。
Observables suffer from the same side effects of error handling, but generally the error messages are of better quality. Observable 遭受与错误处理相同的副作用,但通常错误消息的质量更好。 If someone uses
throwError(new Error())
the Error object will contain a stack trace, and if you're using the HttpModule
the Error object is usually a Http response object that tells you about the request.如果有人使用
throwError(new Error())
则Error对象将包含堆栈跟踪,如果您使用的是HttpModule
则Error对象通常是一个Http 响应对象,它会告诉您有关请求的信息。
So the moral of the story here: Catch your errors, use observables when you can and don't use async ngOnInit()
, because it will come back to haunt you as a difficult bug to find and fix.所以这里的故事的寓意是:捕捉你的错误,在你可以的时候使用 observables 并且不要使用
async ngOnInit()
,因为它会作为一个难以找到和修复的错误再次困扰你。
I wonder what are the downsides of an immediately invoked function expression :我想知道立即调用函数表达式的缺点是什么:
ngOnInit () {
(async () => {
const data = await this.service.getData();
this.data = this.modifyMyData(data);
})();
}
It is the only way I can imagine to make it work without declaring ngOnInit()
as an async
function这是我能想象到的唯一方法是让它在不将
ngOnInit()
声明为async
函数的情况下工作
You can use rxjs function of
.您可以使用rxjs功能
of
。
of(this.service.getData());
Converts the promise to an observable sequence.将承诺转换为可观察序列。
I used try catch inside the ngOnInit():我在 ngOnInit() 中使用了 try catch:
async ngOnInit() {
try {
const user = await userService.getUser();
} catch (error) {
console.error(error);
}
}
Then you get a more descriptive error and you can find where the bug is然后你会得到一个更具描述性的错误,你可以找到错误在哪里
If getData() returns an observable, you could change it to a promise and resolve the promise with take(1).如果 getData() 返回一个 observable,您可以将其更改为承诺并使用 take(1) 解决该承诺。
import { take } from 'rxjs/operators';
async ngOnInit(): Promise<any> {
const data = await this.service.getData().pipe(take(1)).toPromise();
this.data = this.modifyMyData(data);
}
I would do this a bit differently, you don't show your html template, but I'm assuming you're doing something in there with data, ie我会做一些不同的事情,你不显示你的 html 模板,但我假设你正在用数据做一些事情,即
<p> {{ data.Name }}</p> <!-- or whatever -->
Is there a reason you're not using the async pipe?, ie您是否有理由不使用异步管道?,即
<p> {{ (data$ | async).Name }}</p>
or或者
<p *ngIf="(data$ | async) as data"> {{ data.name }} </p>
and in your ngOnInit
:在你的
ngOnInit
:
data$: Observable<any>; //change to your data structure
ngOnInit () {
this.data$ = this.service.getData().pipe(
map(data => this.modifyMyData(data))
);
}
The correct answer to this question is to use Resolver feature of Angular.这个问题的正确答案是使用 Angular 的Resolver功能。 With Resolve, your component will not be rendered until you tell it to render.
使用 Resolve,在您告诉它渲染之前,您的组件不会被渲染。
From docs:来自文档:
Interface that classes can implement to be a data provider.
类可以实现为数据提供者的接口。 A data provider class can be used with the router to resolve data during navigation.
数据提供者类可以与路由器一起使用,以在导航期间解析数据。 The interface defines a resolve() method that is invoked when the navigation starts.
该接口定义了一个在导航开始时调用的 resolve() 方法。 The router waits for the data to be resolved before the route is finally activated.
路由器在路由最终激活之前等待数据解析。
To put it plainly, what you are doing, is not reasonable .说白了,你的所作所为,是不合理的。 If you what some data to be available before you navigate to that page, use a resolver in your route.
如果您在导航到该页面之前有哪些可用数据,请在您的路线中使用解析器。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.