简体   繁体   English

Angular`ngOnInit`中的异步/等待

[英]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 ,这样我就可以使用asyncawait并获得更直观的代码风格。

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并使用asyncawait时,它变为:

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对象将包含堆栈跟踪,如果您使用的是HttpModuleError对象通常是一个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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM