次の例を考えてみましょう。
_Observable.range(1, 10).subscribe(i -> {
System.out.println(i);
if (i == 5) {
throw new RuntimeException("oops!");
}
}, Throwable::printStackTrace);
_
これにより、1から5までの数値が出力され、例外が出力されます。
私が達成したいのは、オブザーバーをサブスクライブしたままにし、例外をスローした後も実行を継続することです。つまり、1から10までのすべての数値を出力します。
retry()
と 他のさまざまなエラー処理演算子 を使用してみましたが、ドキュメントに記載されているように、それらの目的は、observable自体によって発行されたエラーを処理することです。
最も簡単な解決策は、onNext
の全体をtry-catchブロックにラップすることですが、それは私には良い解決策のようには思えません。 同様のRx.NET質問 で、提案された解決策は、監視可能なプロキシを作成することによってラッピングを行う拡張メソッドを作成することでした。私はそれを作り直そうとしました:
_Observable<Integer> Origin = Observable.range(1, 10);
Observable<Integer> proxy = Observable.create((Observable.OnSubscribe<Integer>) s ->
Origin.subscribe(i -> {try { s.onNext(i); } catch (Exception ignored) {}}, s::onError, s::onCompleted));
proxy.subscribe(i -> {
System.out.println(i);
if (i == 5) {
throw new RuntimeException("oops!");
}
}, Throwable::printStackTrace);
_
RxJava自体がサブスクライバーを SafeSubscriber
にラップするため、これは何も変更しません。 unsafeSubscribe
を使用して回避することも、適切な解決策ではないようです。
この問題を解決するにはどうすればよいですか?
これは、Rxを学習するときに発生する一般的な質問です。
サブスクライバーに例外処理ロジックを配置するという提案は、一般的な監視可能なラッパーを作成するよりも望ましい方法です。
Rxは、サブスクライバーにイベントをプッシュすることを目的としていることを忘れないでください。
オブザーバブルインターフェースから、イベントの処理にかかった時間、またはスローされた例外に含まれる情報以外に、オブザーバブルがサブスクライバーについて知ることができることは実際には何もないことは明らかです。
サブスクライバーの例外を処理し、そのサブスクライバーにイベントを送信し続けるための汎用ラッパーは、お勧めできません。
どうして?オブザーバブルは、サブスクライバーが現在不明な障害状態にあることを実際に知っているだけです。この状況でイベントの送信を続行することは賢明ではありません。たとえば、サブスクライバーは、この時点以降のすべてのイベントが例外をスローし、それを実行するのにしばらく時間がかかる状態にあります。
サブスクライバーが例外をスローすると、オブザーバブルの実行可能なアクションは2つだけになります。
サブスクライバー例外の特定の処理は、設計上の選択としては不適切です。サブスクライバーとオブザーバブルの間に不適切な動作の結合が発生します。したがって、悪いサブスクライバーに対して回復力を持たせたい場合、上記の2つの選択肢は、実際には、オブザーバブル自体の責任の賢明な限界です。
subscriberに復元力を持たせて続行したい場合は、特定の例外から回復する方法を知っている(およびおそらく、一時的な例外、ロギング、再試行ロジック、回路遮断などを処理するためです)。
サブスクライバー自体だけが、障害が発生した場合にさらにイベントを受信するのに適しているかどうかを理解するためのコンテキストを持ちます。
状況によって再利用可能なエラー処理ロジックの開発が必要な場合は、observable-ではなく、オブザーバーのイベントハンドラーをラップするという考え方に身を置き、失敗に直面してもやみくもにイベントの送信を続けないように注意してください。 Release It! Rxについては書かれていませんが、この最後の点について言うことがたくさんある面白いソフトウェアエンジニアリングの古典です。まだ読んでいない方は、ぜひお勧めします。