web-dev-qa-db-ja.com

Angular `ngOnInit`のasync / await

私は現在、Angularのrespを置き換えることの賛否両論を評価しています。 ObservablePromiseを使用して、より直感的なコードスタイルを取得できるように、RxJSのasyncを単純なawaitとともに使用します。

典型的なシナリオの1つ:ngOnInit内にデータをロードします。 Observablesを使用して、次のことを行います。

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

代わりにPromisegetData()から返し、asyncおよびawaitを使用すると、次のようになります。

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

Angularは「知る」ことはできません。ngOnInitasyncになったことは明らかです。これは問題ではないと感じています。私のアプリはまだしかし、OnInitインターフェースを見ると、関数は明らかにasyncと宣言できることを示唆するような方法で宣言されていません。

_ngOnInit(): void;
_

だから-結論:私がここでやっていることは理にかなっていますか?または、予期しない問題が発生しますか?

26
qqilihq

以前と同じです。 ngOnInitはPromiseを返し、呼び出し元はそのPromiseを無視します。つまり、呼び出し元は、メソッド内のすべてが完了するのを待たずに続行します。この特定のケースでは、ビューの構成が完了し、ビューがthis.dataが設定されています。

それは以前と同じ状況です。呼び出し元はサブスクリプションが完了するのを待たず、アプリを起動してからthis.dataが入力されていました。ビューがdataに依存している場合は、アクセスできないようにngIfを設定している可能性があります。

個人的には、その影響を認識している限り、これをぎこちない、または悪い習慣だとは思いません。ただし、ngIfは面倒な場合があります(どちらの方法でも必要になります)。個人的には、この状況を回避できるように、意味のあるルートリゾルバーを使用することにしました。ルートのナビゲートが完了する前にデータが読み込まれ、ビューが読み込まれる前にデータが利用可能であることがわかります。

23
Pace

さて、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>]
_

どうした?スタックトレースがコンポーネントの外側にあるため、手掛かりはありません。

それでも、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>]
_

繰り返しになりますが、ここではコンテキストが失われており、バグを修正するための場所がわかりません。

オブザーバブルはエラー処理と同じ副作用を受けますが、一般にエラーメッセージはより良い品質です。誰かがthrowError(new Error())を使用する場合、Errorオブジェクトにはスタックトレースが含まれ、HttpModuleを使用している場合、Errorオブジェクトは通常は、リクエストについて通知するHttp responseオブジェクトです。

つまり、ここでの話の教訓:エラーをキャッチし、async ngOnInit()を使用できる場合はオブザーバブルを使用します。これは、見つけて修正するのが難しいバグとして悩まされるためです。

6
Reactgular

Rxjs関数ofを使用できます。

of(this.service.getData());

Promiseを監視可能なシーケンスに変換します。

2
ocknamo-bb

GetData()がオブザーバブルを返す場合、それをpromiseに変更し、take(1)を使用してpromiseを解決できます。

import { take } from 'rxjs/operators';

async ngOnInit(): Promise<any> {
  const data = await this.service.getData().pipe(take(1)).toPromise();
  this.data = this.modifyMyData(data);
}
0
Jonathan