繁体   English   中英

Angular`ngOnInit`中的异步/等待

[英]async/await in Angular `ngOnInit`

我目前正在评估替换 Angular 的利弊。 RxJS 的Observable带有普通的Promise ,这样我就可以使用asyncawait并获得更直观的代码风格。

我们的典型场景之一:在ngOnInit中加载一些数据。 使用Observables ,我们可以:

ngOnInit () {
  this.service.getData().subscribe(data => {
    this.data = this.modifyMyData(data);
  });
}

当我从getData()返回Promise并使用asyncawait时,它变为:

async ngOnInit () {
  const data = await this.service.getData();
  this.data = this.modifyMyData(data);
}

现在,很明显,Angular 不会“知道” ngOnInit已经变成了async 我觉得这不是问题:我的应用程序仍然像以前一样工作。 但是当我查看OnInit接口时,该函数显然没有以表明它可以声明为async的方式声明:

ngOnInit(): void;

所以——底线:我在这里做的事情合理吗? 还是我会遇到任何不可预见的问题?

它和你以前的没有什么不同。 ngOnInit将返回一个 Promise 并且调用者将忽略该承诺。 这意味着调用者在继续之前不会等待方法中的所有内容完成。 在这种特定情况下,这意味着视图将完成配置,并且视图可能会在设置this.data之前启动。

这和你之前遇到的情况一样。 调用者不会等待您的订阅完成,并且可能会在填充this.data之前启动应用程序。 如果您的视图依赖于data那么您可能有某种ngIf设置来阻止您访问它。

我个人并不认为这是尴尬或不好的做法,只要你意识到其中的含义。 但是, ngIf可能很乏味(无论哪种方式都需要它们)。 我个人已经转向在有意义的地方使用路由解析器,这样我就可以避免这种情况。 在路线完成导航之前加载数据,我可以在加载视图之前知道数据可用。

现在,很明显,Angular 不会“知道”ngOnInit 已经变成异步了。 我觉得这不是问题:我的应用程序仍然像以前一样工作。

从语义上讲,它可以正常编译并按预期运行,但是编写async / wait的便利是以错误处理为代价的,我认为应该避免这种情况。

让我们看看会发生什么。

当 promise 被拒绝时会发生什么:

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>]

同样,我们在这里失去了上下文,不知道去哪里修复错误。

Observable 遭受与错误处理相同的副作用,但通常错误消息的质量更好 如果有人使用throwError(new Error())Error对象将包含堆栈跟踪,如果您使用的是HttpModuleError对象通常是一个Http 响应对象,它会告诉您有关请求的信息。

所以这里的故事的寓意是:捕捉你的错误,在你可以的时候使用 observables 并且不要使用async ngOnInit() ,因为它会作为一个难以找到和修复的错误再次困扰你。

我想知道立即调用函数表达式的缺点是什么:

    ngOnInit () {
      (async () => {
        const data = await this.service.getData();
        this.data = this.modifyMyData(data);
      })();
    }

这是我能想象到的唯一方法是让它在不将ngOnInit()声明为async函数的情况下工作

您可以使用rxjs功能of

of(this.service.getData());

将承诺转换为可观察序列。

我在 ngOnInit() 中使用了 try catch:

async ngOnInit() {      
   try {           
       const user = await userService.getUser();
    } catch (error) {           
        console.error(error);       
    }    
} 

然后你会得到一个更具描述性的错误,你可以找到错误在哪里

如果 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);
}

我会做一些不同的事情,你不显示你的 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))
  );
}

这个问题的正确答案是使用 Angular 的Resolver功能。 使用 Resolve,在您告诉它渲染之前,您的组件不会被渲染。

来自文档:

类可以实现为数据提供者的接口。 数据提供者类可以与路由器一起使用,以在导航期间解析数据。 该接口定义了一个在导航开始时调用的 resolve() 方法。 路由器在路由最终激活之前等待数据解析。

说白了,你的所作所为,是不合理的。 如果您在导航到该页面之前有哪些可用数据,请在您的路线中使用解析器

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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