K.Os
K.Os

Reputation: 5506

How to use retryWhen only 3 times then give up

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

Answers (3)

Rafael
Rafael

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

Marcin Jedynak
Marcin Jedynak

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

Anatolii
Anatolii

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

Related Questions