rxjava: Can I use retry() but with delay?

后端 未结 14 2210
别那么骄傲
别那么骄傲 2020-11-28 18:25

I am using rxjava in my Android app to handle network requests asynchronously. Now I would like to retry a failed network request only after a certain time has passed.

相关标签:
14条回答
  • 2020-11-28 19:06

    retryWhen is a complicated, perhaps even buggy, operator. The official doc and at least one answer here use range operator, which will fail if there are no retries to be made. See my discussion with ReactiveX member David Karnok.

    I improved upon kjones' answer by changing flatMap to concatMap and by adding a RetryDelayStrategy class. flatMap doesn't preserve order of emission while concatMap does, which is important for delays with back-off. The RetryDelayStrategy, as the name indicates, let's the user choose from various modes of generating retry delays, including back-off. The code is available on my GitHub complete with the following test cases:

    1. Succeeds on 1st attempt (no retries)
    2. Fails after 1 retry
    3. Attempts to retry 3 times but succeeds on 2nd hence doesn't retry 3rd time
    4. Succeeds on 3rd retry

    See setRandomJokes method.

    0 讨论(0)
  • 2020-11-28 19:06

    You can add a delay in the Observable returned in the retryWhen Operator

              /**
     * Here we can see how onErrorResumeNext works and emit an item in case that an error occur in the pipeline and an exception is propagated
     */
    @Test
    public void observableOnErrorResumeNext() {
        Subscription subscription = Observable.just(null)
                                              .map(Object::toString)
                                              .doOnError(failure -> System.out.println("Error:" + failure.getCause()))
                                              .retryWhen(errors -> errors.doOnNext(o -> count++)
                                                                         .flatMap(t -> count > 3 ? Observable.error(t) : Observable.just(null).delay(100, TimeUnit.MILLISECONDS)),
                                                         Schedulers.newThread())
                                              .onErrorResumeNext(t -> {
                                                  System.out.println("Error after all retries:" + t.getCause());
                                                  return Observable.just("I save the world for extinction!");
                                              })
                                              .subscribe(s -> System.out.println(s));
        new TestSubscriber((Observer) subscription).awaitTerminalEvent(500, TimeUnit.MILLISECONDS);
    }
    

    You can see more examples here. https://github.com/politrons/reactive

    0 讨论(0)
  • 2020-11-28 19:10

    instead of using MyRequestObservable.retry I use a wrapper function retryObservable(MyRequestObservable, retrycount, seconds) which return a new Observable that handle the indirection for the delay so I can do

    retryObservable(restApi.getObservableStuff(), 3, 30)
        .subscribe(new Action1<BonusIndividualList>(){
            @Override
            public void call(BonusIndividualList arg0) 
            {
                //success!
            }
        }, 
        new Action1<Throwable>(){
            @Override
            public void call(Throwable arg0) { 
               // failed after the 3 retries !
            }}); 
    
    
    // wrapper code
    private static <T> Observable<T> retryObservable(
            final Observable<T> requestObservable, final int nbRetry,
            final long seconds) {
    
        return Observable.create(new Observable.OnSubscribe<T>() {
    
            @Override
            public void call(final Subscriber<? super T> subscriber) {
                requestObservable.subscribe(new Action1<T>() {
    
                    @Override
                    public void call(T arg0) {
                        subscriber.onNext(arg0);
                        subscriber.onCompleted();
                    }
                },
    
                new Action1<Throwable>() {
                    @Override
                    public void call(Throwable error) {
    
                        if (nbRetry > 0) {
                            Observable.just(requestObservable)
                                    .delay(seconds, TimeUnit.SECONDS)
                                    .observeOn(mainThread())
                                    .subscribe(new Action1<Observable<T>>(){
                                        @Override
                                        public void call(Observable<T> observable){
                                            retryObservable(observable,
                                                    nbRetry - 1, seconds)
                                                    .subscribe(subscriber);
                                        }
                                    });
                        } else {
                            // still fail after retries
                            subscriber.onError(error);
                        }
    
                    }
                });
    
            }
    
        });
    
    }
    
    0 讨论(0)
  • 2020-11-28 19:13

    This example works with jxjava 2.2.2:

    Retry without delay:

    Single.just(somePaylodData)
       .map(data -> someConnection.send(data))
       .retry(5)
       .doOnSuccess(status -> log.info("Yay! {}", status);
    

    Retry with delay:

    Single.just(somePaylodData)
       .map(data -> someConnection.send(data))
       .retryWhen((Flowable<Throwable> f) -> f.take(5).delay(300, TimeUnit.MILLISECONDS))
       .doOnSuccess(status -> log.info("Yay! {}", status)
       .doOnError((Throwable error) 
                    -> log.error("I tried five times with a 300ms break" 
                                 + " delay in between. But it was in vain."));
    

    Our source single fails if someConnection.send() fails. When that happens, the observable of failures inside retryWhen emits the error. We delay that emission by 300ms and send it back to signal a retry. take(5) guarantees that our signaling observable will terminate after we receive five errors. retryWhen sees the termination and doesn't retry after the fifth failure.

    0 讨论(0)
  • 2020-11-28 19:13

    Based on kjones answer here is Kotlin version of RxJava 2.x retry with a delay as an extension. Replace Observable to create the same extension for Flowable.

    fun <T> Observable<T>.retryWithDelay(maxRetries: Int, retryDelayMillis: Int): Observable<T> {
        var retryCount = 0
    
        return retryWhen { thObservable ->
            thObservable.flatMap { throwable ->
                if (++retryCount < maxRetries) {
                    Observable.timer(retryDelayMillis.toLong(), TimeUnit.MILLISECONDS)
                } else {
                    Observable.error(throwable)
                }
            }
        }
    }
    

    Then just use it on observable observable.retryWithDelay(3, 1000)

    0 讨论(0)
  • 2020-11-28 19:14

    in the event when you need to print out the retry count, you can use the example provided in Rxjava's wiki page https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators

    observable.retryWhen(errors ->
        // Count and increment the number of errors.
        errors.map(error -> 1).scan((i, j) -> i + j)  
           .doOnNext(errorCount -> System.out.println(" -> query errors #: " + errorCount))
           // Limit the maximum number of retries.
           .takeWhile(errorCount -> errorCount < retryCounts)   
           // Signal resubscribe event after some delay.
           .flatMapSingle(errorCount -> Single.timer(errorCount, TimeUnit.SECONDS));
    
    0 讨论(0)
提交回复
热议问题