Reputation: 5506
I want to filter when specific exception occurs during execution of some of the upper chain function and try to retry the whole process only 3 times then if it still failes then give up. I came to something like this:
val disposable = someFunction(someParameter, delay, subject)
.flatMapCompletable { (parameter1, parameter2) ->
anotherFunction(parameter1, parameter2, subject)
}
.retryWhen { throwable ->
throwable.filter {
it.cause?.cause is ExampleException1
|| it.cause?.cause is ExampleException2
|| it.cause is ExampleException3
}
}
.andThen(someStuff())
.subscribe({
Timber.d("Finished!")
}, {
Timber.d("Failed!")
})
How to do it properly?
Upvotes: 2
Views: 6298
Reputation: 6369
Code with exponential delay:
YourSingle()
.retryWhen { errors: Flowable<Throwable> ->
errors.zipWith(
Flowable.range(1, retryLimit + 1),
BiFunction<Throwable, Int, Int> { error: Throwable, retryCount: Int ->
if (error is RightTypeOfException && retryCount < retryLimit) {
retryCount
} else {
throw error
}
}
).flatMap { retryCount ->
//exponential 1, 2, 4
val delay = 2.toDouble().pow(retryCount.toDouble()).toLong() / 2
Flowable.timer(delay, TimeUnit.SECONDS)
}
}
Upvotes: 0
Reputation: 3847
You may use zipWith
with a range
to achieve this.
.retryWhen { errors -> errors.zipWith(Observable.range(1, 3), { _, i -> i }) }
The retryWhen
operator gives you the stream of all the errors from your source publisher. Here you zip these with numbers 1, 2, 3. Therefore the resulting stream will emit 3 next
followed by the complete
. Contrary to what you may think this resubscribes only twice, as the complete
emitted immediately after the third next
causes the whole stream to complete.
You may extend this further, by retrying only for some errors, while immediately failing for others. For example, if you want to retry only for IOException
, you may extend the above solution to:
.retryWhen { errors -> errors
.zipWith(Observable.range(1, 3), { error, _ -> error })
.map { error -> when (error) {
is IOException -> error
else -> throw error
}}
}
Since map
cannot throw a checked exception in Java, Java users may use flatMap
for the same purpose.
Upvotes: 4
Reputation: 14680
I think what you're trying to do can be achieved with retry
exclusively:
val observable = Observable.defer {
System.out.println("someMethod called")
val result1 = 2 // some value from someMethod()
Observable.just(result1)
}
observable.flatMap { result ->
// another method is called here but let's omit it for the sake of simplicity and throw some exception
System.out.println("someMethod2 called")
throw IllegalArgumentException("Exception someMethod2")
Observable.just("Something that won't be executed anyways")
}.retry { times, throwable ->
System.out.println("Attempt# " + times)
// if this condition is true then the retry will occur
times < 3 && throwable is IllegalArgumentException
}.subscribe(
{ result -> System.out.println(result) },
{ throwable -> System.out.println(throwable.localizedMessage) })
Output:
someMethod called
someMethod2 called
Attempt# 1
someMethod called
someMethod2 called
Attempt# 2
someMethod called
someMethod2 called
Attempt# 3
Exception someMethod2
As someMethod2
always throws an Exception
, after 3 attempts Exception someMethod2
is printed in onError
of the observer.
Upvotes: 1