RetryWhenのドキュメント の例では、次のようになります。
_Observable.create((Subscriber<? super String> s) -> {
System.out.println("subscribing");
s.onError(new RuntimeException("always fails"));
}).retryWhen(attempts -> {
return attempts.zipWith(Observable.range(1, 3), (n, i) -> i).flatMap(i -> {
System.out.println("delay retry by " + i + " second(s)");
return Observable.timer(i, TimeUnit.SECONDS);
});
}).toBlocking().forEach(System.out::println);
_
しかし、再試行がなくなった場合、どうすればエラーを伝播できますか?
.doOnError(System.out::println)
afterを追加しても、retryWhen
句はエラーをキャッチしません。それも放出されていますか?
.doOnError(System.out::println)
beforeretryWhenを追加すると、すべての再試行に対して_always fails
_が表示されます。
retryWhen
のドキュメントには、onError
通知をサブスクライバーに渡して終了すると記載されています。したがって、次のようなことができます。
final int ATTEMPTS = 3;
Observable.create((Subscriber<? super String> s) -> {
System.out.println("subscribing");
s.onError(new RuntimeException("always fails"));
}).retryWhen(attempts -> attempts
.zipWith(Observable.range(1, ATTEMPTS), (n, i) ->
i < ATTEMPTS ?
Observable.timer(i, SECONDS) :
Observable.error(n))
.flatMap(x -> x))
.toBlocking()
.forEach(System.out::println);
Javadoc for retryWhen
は、次のように述べています。
そのObservableがonCompleteまたはonErrorを呼び出す場合、再試行は子サブスクリプションでonCompletedまたはonErrorを呼び出します。
簡単に言うと、例外を伝播する場合は、十分な再試行が完了したら、元の例外を再スローする必要があります。
簡単な方法は、_Observable.range
_を再試行する回数より1大きい値に設定することです。
次に、Zip関数で、現在の再試行回数をテストします。 NUMBER_OF_RETRIES + 1に等しい場合は、Observable.error(throwable)
を返すか、例外を再スローします。
例えば
_Observable.create((Subscriber<? super String> s) -> {
System.out.println("subscribing");
s.onError(new RuntimeException("always fails"));
}).retryWhen(attempts -> {
return attempts.zipWith(Observable.range(1, NUMBER_OF_RETRIES + 1), (throwable, attempt) -> {
if (attempt == NUMBER_OF_RETRIES + 1) {
throw Throwables.propagate(throwable);
}
else {
return attempt;
}
}).flatMap(i -> {
System.out.println("delaying retry by " + i + " second(s)");
return Observable.timer(i, TimeUnit.SECONDS);
});
}).toBlocking().forEach(System.out::println);
_
余談ですが、doOnError
はObservableにまったく影響を与えません。エラーが発生した場合に、何らかのアクションを実行するためのフックを提供するだけです。一般的な例はロギングです。
1つのオプションは、Observable.materialize()
を使用してObservable.range()
アイテムを通知に変換することです。次に、onCompleted()
が発行されると、エラーをダウンストリームに伝播できます(以下のサンプルでは、Pair
を使用してObservable.range()
通知とObservable
からの例外をラップしています)
@Test
public void retryWhen() throws Exception {
Observable.create((Subscriber<? super String> s) -> {
System.out.println("subscribing");
s.onError(new RuntimeException("always fails"));
}).retryWhen(attempts -> {
return attempts.zipWith(Observable.range(1, 3).materialize(), Pair::new)
.flatMap(notifAndEx -> {
System.out.println("delay retry by " + notifAndEx + " second(s)");
return notifAndEx.getRight().isOnCompleted()
? Observable.<Integer>error(notifAndEx.getLeft())
: Observable.timer(notifAndEx.getRight().getValue(), TimeUnit.SECONDS);
});
}).toBlocking().forEach(System.out::println);
}
private static class Pair<L,R> {
private final L left;
private final R right;
public Pair(L left, R right) {
this.left = left;
this.right = right;
}
public L getLeft() {
return left;
}
public R getRight() {
return right;
}
}
Maven Centralにある rxjava-extras のRetryWhen
ビルダーを使用して、必要な動作を取得できます。最新バージョンを使用してください。
Observable.create((Subscriber<? super String> s) -> {
System.out.println("subscribing");
s.onError(new RuntimeException("always fails"));
})
.retryWhen(RetryWhen
.delays(Observable.range(1, 3)
.map(n -> (long) n),
TimeUnit.SECONDS).build())
.doOnError(e -> e.printStackTrace())
.toBlocking().forEach(System.out::println);