Angular ngOnInit中的异步/等待

qqi*_*ihq 12 asynchronous promise observable typescript angular

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

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

ngOnInit () {
  this.service.getData().subscribe(data => {
    this.data = this.modifyMyData(data);
  });
}
Run Code Online (Sandbox Code Playgroud)

当我改为返回Promisefrom getData(),并使用async和时await,它变为:

async ngOnInit () {
  const data = await this.service.getData();
  this.data = this.modifyMyData(data);
}
Run Code Online (Sandbox Code Playgroud)

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

ngOnInit(): void;
Run Code Online (Sandbox Code Playgroud)

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

Rea*_*lar 27

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

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

让我们看看会发生什么。

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

public ngOnInit() {
    const p = new Promise((resolver, reject) => reject(-1));
}
Run Code Online (Sandbox Code Playgroud)

以上生成了以下堆栈跟踪:

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]
    .....
Run Code Online (Sandbox Code Playgroud)

我们可以清楚地看到未处理的错误是由 a 触发的ngOnInit,还可以看到哪个源代码文件可以找到有问题的代码行。

当我们使用async/wait拒绝时会发生什么:

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]
    .....
Run Code Online (Sandbox Code Playgroud)

以上生成了以下堆栈跟踪:

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>]
Run Code Online (Sandbox Code Playgroud)

发生了什么?我们不知道,因为堆栈跟踪在组件之外。

不过,您可能会倾向于使用 Promise 而只是避免使用async / wait. 所以让我们看看如果一个 promise 在 a 之后被拒绝会发生什么setTimeout()

    public async ngOnInit() {
        const p = await new Promise((resolver, reject) => reject());
    }
Run Code Online (Sandbox Code Playgroud)

我们将得到以下堆栈跟踪:

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>]
Run Code Online (Sandbox Code Playgroud)

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

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

所以这里的故事的寓意是:抓住你的错误,在你可以和不使用的情况下使用 observables async ngOnInit(),因为它会作为一个难以找到和修复的错误再次困扰你。


Pac*_*ace 13

这与您之前的没什么不同。 ngOnInit将返回一个Promise,而调用方将忽略该Promise。这意味着调用方不会继续进行方法中的所有操作。在这种特定情况下,这意味着该视图将完成配置,并且该视图可在this.data设置之前启动。

这与您以前的情况相同。呼叫者不会等待您的订阅完成,因此可能会this.data在填充之前启动应用程序。如果您的视图依赖于此,data则可能需要进行某种ngIf设置以防止您访问它。

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

  • 感谢您提到路由解析器。我认为这是解决这个经常出现的问题的更好方法。 (2认同)

小智 6

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

    ngOnInit () {
      (async () => {
        const data = await this.service.getData();
        this.data = this.modifyMyData(data);
      })();
    }
Run Code Online (Sandbox Code Playgroud)

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


Jon*_*han 5

ngOnInit 不等待承诺完成。如果您想像这样使用await,您可以将其设为异步函数:

import { take } from 'rxjs/operators';

async ngOnInit(): Promise<any> {
  const data = await firstValueFrom(this.service.getData());
  this.data = this.modifyMyData(data);
}
Run Code Online (Sandbox Code Playgroud)

但是,如果您使用ngOnInit构造函数而不是等待函数完成,则基本上相当于执行以下操作:

import { take } from 'rxjs/operators';

constructor() {
  firstValueFrom(this.service.getData())
    .then((data => {;
      this.data = this.modifyMyData(data);
    });
}
Run Code Online (Sandbox Code Playgroud)

它将运行异步函数,但不会等待它完成。如果您注意到有时它会完成,有时则不会,那么这实际上仅取决于您的函数的时间安排。

使用这篇文章中的想法,您基本上可以跑到外面zone.jsNgZone不包括scheduleMacroTask,但zone.js已导入到 Angular 中。

解决方案

import { isObservable, Observable } from 'rxjs';
import { take } from 'rxjs/operators';

declare const Zone: any;

async waitFor<T>(prom: Promise<T> | Observable<T>): Promise<T> {
  if (isObservable(prom)) {
    prom = firstValueFrom(prom);
  }
  const macroTask = Zone.current
    .scheduleMacroTask(
      `WAITFOR-${Math.random()}`,
      () => { },
      {},
      () => { }
    );
  return prom.then((p: T) => {
    macroTask.invoke();
    return p;
  });
}
Run Code Online (Sandbox Code Playgroud)

我个人把这个函数放在 my 中core.module.ts,尽管你可以把它放在任何地方。

像这样使用它:

constructor(private cm: CoreModule) { 
  const p = this.service.getData();
  this.post = this.cm.waitFor(p);
}
Run Code Online (Sandbox Code Playgroud)

您还可以检查isBrowser以保持可观察性,或等待结果。

相反,您也可以像本文angular-zen中一样导入和使用它,尽管您将导入比您需要的更多的内容。

J

更新:2/26/22 - 现在使用firstValueFrom,因为.toPromise()已折旧。


Era*_* BO 5

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

async ngOnInit() {      
   try {           
       const user = await userService.getUser();
    } catch (error) {           
        console.error(error);       
    }    
} 
Run Code Online (Sandbox Code Playgroud)

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