Angular ngOnInit中的异步/等待

时间:2019-05-11 16:10:07

标签: angular typescript asynchronous promise observable

我目前正在评估取代Angular的优点的利弊。 RxJS的Observable和普通的Promise,以便我可以使用asyncawait并获得更直观的代码风格。

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

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

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

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

现在,很显然,Angular不会“知道” ngOnInit已变成async。我觉得这不是问题:我的应用仍然可以像以前一样工作。但是,当我查看OnInit接口时,显然并未以建议将其声明为async的方式声明该函数:

ngOnInit(): void;

所以-底线:我在这里做什么合理吗?还是我会遇到任何无法预料的问题?

7 个答案:

答案 0 :(得分:6)

与您之前的没什么不同。 for al in aliens.sprites(): #use al to make the single sprite shooting 将返回一个Promise,而呼叫者将忽略该承诺。这意味着调用方不会继续进行方法中的所有操作。在这种特定情况下,这意味着该视图将完成配置,并且该视图可能在设置ngOnInit之前启动。

这与您以前的情况相同。调用方不会等待您的订阅完成,并且可能会在填充this.data之前启动该应用程序。如果您的视图依赖于this.data,则可能需要进行某种data设置来阻止您访问它。

我个人认为这并不尴尬或不当行为,只要您知道其中的含义即可。但是,ngIf可能很乏味(无论哪种方式都需要)。我个人已经转向使用有意义的路由解析器,因此可以避免这种情况。在路线完成导航之前就已经加载了数据,而且我知道在加载视图之前就可以使用这些数据。

答案 1 :(得分:5)

我想知道 immediately invoked function expression 的缺点是什么:

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

这是我能想到的唯一方法,无需将 ngOnInit() 声明为 async 函数

答案 2 :(得分:1)

您可以使用rxjs函数of

of(this.service.getData());

将诺言转换为可观察的顺序。

答案 3 :(得分:1)

  

现在,很显然,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>]

发生了什么事?我们没有任何线索,因为堆栈跟踪位于组件外部。

不过,您可能会很想使用诺言,而只是避免使用async / wait。因此,让我们看看如果在setTimeout()之后拒绝了一个承诺,会发生什么情况。

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

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

可观察对象遭受错误处理的相同副作用,但是通常错误消息的质量更好。如果有人使用throwError(new Error()),则 Error 对象将包含堆栈跟踪,如果您使用HttpModule,则 Error 对象通常是<告诉您有关请求的em> Http response 对象。

这里的故事的寓意是:捕获错误,在可以使用且不使用async ngOnInit()的情况下使用可观察的事物,因为它会再次困扰您,成为难以查找和修复的错误。

答案 4 :(得分:1)

我曾经尝试在ngOnInit()内捕获:

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

然后您将得到更具描述性的错误,并且可以找到错误所在的地方

答案 5 :(得分:0)

如果getData()返回一个可观察对象,则可以将其更改为一个承诺,并使用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);
}

答案 6 :(得分:0)

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