RxJsのエラー処理。 完全な実践ガイド

Error handling is an essential part of RxJs, as we will need in just about any reactive program that we write.

RxJS のエラー処理は、ライブラリの他の部分ほどよく理解されていないかもしれませんが、最初に Observable 契約全般の理解に集中すれば、実は非常に簡単に理解することができます。

この投稿では、基本 (Observable コントラクト) から始めて、ほとんどの実用的なシナリオをカバーするために必要となる、最も一般的なエラー処理戦略を含む完全なガイドを提供するつもりです。

Table Of Contents

この投稿では、次のトピックをカバーします。

  • Observable 契約とエラー処理
  • RxJs のサブスクライブとエラー コールバック
  • キャッチ エラー演算子
  • Catch and Replace Strategy
  • throwError と Catch and Rethrow Strategy
  • Using multiple times catchError in an Observable chain
  • The finalize Operator
  • The Retry Strategy
  • Then retryWhen Operator
  • Notification Observableの作成
  • Immediate Retry Strategy
  • Delayed Retry Strategy
  • The delayWhen Operator
  • The timer Observable creation function
  • Running Github repository (with code samples)
  • Conclusions

So without further adoosted, それでは、RxJsのエラーハンドリングのディープダイブを始めましょう。

オブザーバブル契約とエラー処理

RxJs のエラー処理を理解するために、まず、任意のストリームが 1 回だけエラーになることを理解する必要があります。 これは Observable 契約によって定義され、ストリームは 0 個以上の値を出力できます。

契約がそのように動作するのは、ランタイムで観察するすべてのストリームが実際に動作する方法であるためです。 たとえば、ネットワーク要求が失敗することがあります。

ストリームは完了することもできます。これは、

  • ストリームがエラーなしでそのライフサイクルを終了したこと
  • 完了後、ストリームはそれ以上値を出力しないこと

完了の代わりとして、ストリームはエラー終了も可能で、これは以下を意味します。

  • ストリームはエラーでそのライフサイクルを終了しました
  • エラーが投げられた後、ストリームは他の値を出力しません

完了とエラーは相互に排他的であることに注意してください。

  • ストリームが完了すると、その後エラーになることはできません
  • ストリームがエラーになると、その後完了できません

また、ストリームが完了またはエラーになる義務はなく、これらの 2 つの可能性はオプションであることに注意してください。

これは、ある特定のストリームがエラーになると、Observable 契約に従って、それをもう使用できないことを意味します。 この時点で、それではエラーからどのように回復できるのかと考えているに違いありません。

RxJs のサブスクライブとエラー コールバック

RxJs エラー処理の動作を実際に見るために、ストリームを作成し、それにサブスクライブしてみましょう。 subscribe 呼び出しは 3 つのオプション引数を取ることを覚えておきましょう。

  • 成功ハンドラ関数:ストリームが値を出力するたびに呼び出される
  • エラーハンドラ関数:エラーが発生した場合にのみ呼び出される。 このハンドラは、エラーそのものを受け取ります。
  • 完了ハンドラ関数は、ストリームが完了した場合にのみ呼び出されます。

完了動作の例

ストリームがエラーにならない場合、コンソールにはこのように表示されます:

HTTP response {payload: Array(9)}HTTP request completed.

見てわかるように、このHTTPストリームでは値が1つだけ送信されて、その後完了しますが、これはエラーが発生しないことを意味します。

しかし、代わりにストリームがエラーをスローした場合はどうなるでしょうか。 その場合、代わりにコンソールに次のように表示されます。

RxJs Error Handling console output

見てのとおり、ストリームは値を送信せず、すぐにエラーになりました。 エラーの後、完了は発生しませんでした。

Subscribe エラー ハンドラーの制限事項

Subscribe 呼び出しを使用してエラーを処理することが必要なすべての場合ですが、このエラー処理アプローチは制限されています。

The catchError Operator

同期プログラミングでは、try 節でコードのブロックをラップし、catch ブロックでスローする可能性のあるエラーをキャッチし、その後エラーを処理するオプションがあります。

以下は、同期的な catch 構文の例です。

このメカニズムは、try/catch ブロック内で発生したすべてのエラーを 1 か所で処理できるため、非常に強力です。

RxJs は、RxJs の catchError 演算子を使用して、この機能に近いものを提供します。

どのように catchError は動作しますか。

RxJs Operator と同様に、catchError は入力 Observable を取り込み、出力 Observable を出す単純な関数です。

キャッチエラー演算子は、エラーになるかもしれない Observable を入力として受け取り、その出力 Observable で入力 Observable の値を出力し始めます。

エラーが発生しない場合、キャッチエラーが生成する出力 Observable は入力 Observable とまったく同じように動作します。

その関数は、エラーが発生したストリームの代替となる Observable を返すと予想されます。

catchError の入力ストリームがエラーになったことを思い出しましょう、したがって、Observable 契約によると、もうそれを使うことはできません。

The Catch and Replace Strategy

キャッチ エラーを使用して、フォールバック値を出力する置換 Observable を提供する方法の例を挙げましょう。

  • CatchError 演算子に、エラー処理関数である関数を渡します。
  • エラー処理関数はすぐには呼び出されず、一般に、通常は呼び出されません
  • 入力ストリームでエラーが発生した場合にのみ、エラー処理関数が呼ばれます
  • catchError の入力 Observable でエラーが発生したら、エラーが発生します。 この関数は、of()関数を用いて構築したObservableを返す
  • of()関数は、1つの値()のみを出力するObservableを構築し、終了する
  • エラー処理関数から、リカバリObservable(of())が返される。 の値は、catchError
  • によって返される出力 Observable の置換値として発行されます。 以下は、コンソールに表示される結果です。
    HTTP response HTTP request completed.

    見てわかるように、subscribe() のエラー処理コールバックは呼び出されなくなりました。

    • 空の配列値 が出力されます
    • http$ Observable が完了します

    見てわかるように、元の Observable がエラーになったにもかかわらず、代替 Observable は http$ の購読者にデフォルトの予備値 () として使用されています。

    置換された Observable を返す前に、いくつかのローカル エラー処理も追加できたことに注意してください!

    これで Catch and Replace 戦略をカバーしましたが、今度は、予備値を提供する代わりに、エラーを再投与するために catchError をどのように使用できるかを見てみましょう。

    そして、それが起こる場合、エラーは、catchError の出力 Observable の購読者に伝播されます。

    このエラー伝播動作は、ローカルでエラーを処理した後、 catchError によってキャッチされたエラーを再スローするメカニズムを私たちに与えています。 次の方法でこれを行うことができます。

    Catch and Rethrow breakdown

    Catch and Rethrow Strategy の実装をステップバイステップで分解してみましょう。

    • 以前と同様に、エラーをキャッチし、代わりの Observable
    • しかし今回は、 のような代わりの出力値を提供する代わりに、ローカルで catchError 関数
    • この場合、単にエラーをコンソールにログ記録しているだけです。 しかし、代わりに、たとえばユーザーにエラー メッセージを表示するなど、任意のローカル エラー処理ロジックを追加できます。 これは、catchError の出力 Observable も、catchError
    • の入力によってスローされたのとまったく同じエラーでエラーになることを意味します。これは、最初に catchError の入力 Observable からその出力 Observable
    • へスローしたエラーをうまく再スローできたということで、残りの Observable チェーンでさらにエラーを処理することができるようになりました。 If needed

    ここで、上記のコードを実行すると、コンソールに次のような結果が表示されます。

    RxJs Error Handling console output

    見てわかるように、期待どおりに、同じエラーが catchError ブロックとサブスクリプション エラー ハンドラー関数の両方で記録されました。

    Observable チェーンで catchError を複数回使用する

    必要に応じて Observable チェーンの異なるポイントで catchError を複数回使用し、チェーン内の各ポイントで異なるエラー戦略を採用できることに注意してください。

    たとえば、Observable チェーンの上位でエラーをキャッチし、それをローカルに処理して再投与し、さらに Observable チェーンの下位で同じエラーを再度キャッチし、今度は (再投与の代わりに) 予備の値を提供することが可能です。

    上記のコードを実行すると、コンソールに次のような出力が表示されます。

    RxJs Map Operator marble diagram

    見てわかるように、エラーは確かに最初に再投与されましたが、subscribe エラー ハンドラ関数に到達することはありません。

    ファイナライズ演算子

    エラーを処理するキャッチ ブロックのほかに、同期 Javascript 構文は、常に実行したいコードを実行するために使用できる最終ブロックも提供します。

    キャッチ ブロック内のコードとは異なり、最終ブロック内のコードは、エラーがスローされたかどうかに関係なく実行されます。

    RxJs では、finalize 演算子と呼ばれる、最終機能に似た動作をする演算子を提供しています。

    Note: Finally は Javascript の予約キーワードであるため、代わりに finalize 演算子と呼ぶことはできません。

    では、このコードを実行して、複数の finalize ブロックがどのように実行されるかを見てみましょう。

    RxJs Error Handling コンソール出力

    最後の finalize ブロックが subscribe value handler および completion handler 関数の後に実行されることに注意してください。

    再試行戦略

    エラーを再スローするかフォールバック値を提供する代わりに、エラーになった Observable にサブスクライブするために単に再試行することも可能です。

    ここで、この方法を説明します。

    • 入力 Observable を取得し、それにサブスクライブして、新しいストリームを作成します。
    • そのストリームがエラーにならない場合、その値を出力に表示させるつもりです。

      ここでの大きな疑問は、いつ入力 Observable を再度購読し、入力ストリームの実行を再試行するのか、ということです。

    • 限られた回数だけ再試行し、出力ストリームをエラーにするのか。

    これらの質問に答えるために、2 番目の補助 Observable が必要になります。 これは、再試行がいつ発生するかを決定する Notifier
    Observable です。

    Notifier Observable は、Retry Strategy の中心である retryWhen 演算子で使用される予定です。

    RxJs retryWhen Operator Marble Diagram

    retryWhen Observable の動作を理解するために、そのマーブル図を見てみましょう。

    RxJs retryWhen Operator

    再試行されている Observable が 1 行目の Observable でなく、上から 2 行目に記載されている 1-2 Observable であることに注目します。

    値 r-r を持つ最初の行の Observable は Notification Observable で、再試行がいつ行われるべきかを決定します。

    Breaking down how retryWhen works

    この図で何が行われているかを分解してみましょう。

    • オブザーバブル1-2は購読され、その値はretryが返す出力オブザーバブルに直ちに反映される
    • オブザーバブル1-2の終了後でも再試行が可能
    • そのとき、通知オブザーバブルが値rを発する。 Observable 1-2が終了した後
    • 通知オブジェクトが出す値(この場合はr)は何でもよい
    • 重要なのはrが出された時点である。 なぜなら、それが1-2 Observableを再試行するきっかけになるからです
    • Observable 1-2 は retryWhen によって再び購読され、その値は retryWhen
    • Observable の出力 Observable に再び反映され、同じことが行われるのです。 新しくサブスクライブされた1-2ストリームの値は、retryWhen
    • の出力に反映され始めますが、その後、その時点で通知Observableは最終的に
    • を完了することになります。 1-2 Observable の進行中の再試行も早期に完了し、値 1 だけが出力され、2 は出力されないことを意味します

    このように、retryWhen は Notification Observable が値を出力するたびに input Observable を再試行するだけです!

    retryWhen がどのように機能するかを理解したので、Notification Observable をどのように作成するかを見てみましょう。

    Notification Observable の作成

    retryWhen 演算子に渡す関数で、Notification Observable を直接作成する必要があります。 この関数は、入力引数として Errors Observable を取り、入力 Observable のエラーを値として出力します。

    したがって、この Errors Observable を購読することにより、エラーが発生したときに正確にそれを知ることができます。

    Immediate Retry Strategy

    エラーが発生した後、失敗したobservableをすぐに再試行するために、私たちがしなければならないのは、Errors Observableをそのまま返すことのみです。

    この場合、ロギング目的でタップ演算子を配管しているだけなので、Errors Observable は変更されません。

    覚えておきましょう、retryWhen 関数呼び出しから返している Observable は Notification Observable です!

    この関数が出力する値は重要ではなく、値が出力されたときのみ重要です。

    即時再試行のコンソール出力

    今、このプログラムを実行すると、コンソールに次の出力があります:

    retryWhen console output

    見てわかるように、HTTP 要求は最初は失敗しましたが、その後再試行を試み、2 回目は正常に通過しています。

    2 回の再試行の間の遅延を、ネットワーク ログを調べることによって見てみましょう。

    RxJs retryWhen network log

    見てわかるように、予想どおり、エラーが発生するとすぐに 2 回目が実行されました。

    Delayed Retry Strategy

    次に、別のエラー回復戦略を実装しましょう。たとえば、エラー発生後、2 秒間待ってから再試行します。

    エラーが断続的な場合、少し時間をおいてから同じ要求を再試行し、2 回目には問題なく要求を通過できるかもしれません。

    タイマー Observable 作成関数

    Delayed Retry 戦略を実装するには、各エラー発生の 2 秒後に値が発行される Notification Observable を作成する必要があります。 このタイマー関数は、いくつかの引数を取るつもりです。

    • 初期遅延、それ以前は値が発行されない
    • 周期間隔、定期的に新しい値を発行したい場合

    次に、タイマー関数のマーブル図を見てみましょう。

    タイマー演算子

    見てわかるように、最初の値0は3秒後にのみ出力され、その後、毎秒新しい値が出力されることになります。

    第 2 引数がオプションであることに注意してください。つまり、これを省略した場合、Observable は 3 秒後に 1 つの値 (0) を出力し、その後完了します。

    この Observable は、再試行を遅らせることができる良いスタートであるように見えます。

    そのため、再試行がいつ行われるべきかを知らせる Notification Observable を定義するチャンスは 1 回しかありません。

    私たちは Errors Observable を使用し、それを delayWhen 演算子に適用して Notification Observable を定義しようとしています。

    このマーブル図において、ソース Observable a-b-c が Errors Observable であり、時間の経過とともに失敗した HTTP エラーを発行していると想像してください:

    The timer Operator

    delayWhen Operator breakdown

    図に従い、delayWhen Operator の仕組みを学びましょう。

    • 入力 Errors Observable の各値は、出力 Observable に表示される前に遅延されます
    • 各値ごとの遅延は異なる場合があり、遅延を決定するために完全に柔軟な方法
    • で作成されるつもりです。 各入力値ごとに delayWhen に渡された関数 (期間セレクタ関数と呼ばれる) を呼び出す Errors Observable
    • その関数は、各入力値の遅延が経過したときを決定する Observable を発行します
    • 各値 a-b-c は、それ自身の期間セレクタ Observable を備えています。 は最終的に 1 つの値 (何でもよい) を発行し、これらの持続時間セレクタ Observable がそれぞれ値を発行すると、完了
    • します。 の出力に表示されます。
    • b が値 c の後の出力に表示されることに注意してください、これは正常です
    • これは、b duration selector Observable(上から3番目の水平線)は c の duration selector Observable の後に値を出力するだけだからです。 の前に c が表示されるのはそのためです。 b

    Delayed Retry Strategy implementation

    さて、これらをまとめて、失敗した HTTP リクエストを各エラーが発生してから 2 秒後に連続して再試行する方法を見てみましょう。

    ここで何が起こっているのか分解してみましょう。

    • retryWhen に渡された関数が 1 回だけ呼び出されることを覚えておきましょう
    • その関数で、エラーが発生するたびに再試行が必要なときに
    • 値を出力する Observable を返しています。 delayWhen演算子は、タイマー関数
    • を呼び出すことで、継続時間セレクタObservableを作成します。この継続時間セレクタObservableは、2秒後に値0を発行し、その後、完了
    • します。 delayWhen Observable は、与えられた入力エラーの遅延が経過したことを知っています
    • その遅延が経過する(エラーが発生してから 2 秒)だけで、エラーは通知 Observable
    • の出力に表示され、値が通知 Observable で出力されます。 retryWhen 演算子は、そのときだけ再試行を実行します

    Retry Strategy Console Output

    それでは、これがコンソールでどのように見えるか見てみましょう! 以下は、最初の 4 回がエラーだったため、5 回再試行された HTTP リクエストの例です:

    The timer Operator

    そして、同じ再試行シーケンスのネットワーク ログです:

    The timer Operator

    見てわかるように、再試行は予想どおりにエラー発生から 2 秒しかたっていませんでした!

    The timer OperatorThe network log: The time of the same retry sequence: The time of a HTTP request is a network log.

    これで、最も一般的に使用される RxJs のエラー処理ストラテジーのガイド ツアーが終了しました。

    実行中の Github リポジトリ (コード サンプル付き)

    これらの複数のエラー処理方法を試すには、失敗する HTTP リクエストの処理を試すことができる動作中のプレイグラウンドを用意することが重要です。

    RxJs サンプル アプリケーション

    結論

    これまで見てきたように、RxJs エラー処理を理解するには、まず Observable 契約の基本を理解することが重要です。

    エラーから回復するためには、唯一の方法は、catchError または retryWhen 演算子の場合に起こるように、エラーになったストリームの代わりとして、何らかの方法で代替ストリームを生成することです。

コメントを残す

メールアドレスが公開されることはありません。