web-dev-qa-db-fra.com

Erreur de capture si retryWhen: s nouvelles tentatives s'épuisent

Dans la documentation pour RetryWhen , l'exemple est le suivant: 

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);

Mais comment puis-je propager l'erreur si les tentatives s'épuisent?

L'ajout de .doOnError(System.out::println) after la clause retryWhen ne corrige pas l'erreur. Est-ce même émis? 

L'ajout d'une .doOnError(System.out::println) before retryWhen affiche always fails pour toutes les tentatives. 

16
Theodor

Le document pour retryWhen indique qu'il transmet la notification onError à ses abonnés et se termine. Donc, vous pouvez faire quelque chose comme ça:

    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);
13
Alexander Perfilyev

Le Javadoc pour retryWhen indique que:

Si cet observable appelle onComplete ou onError, une nouvelle tentative appelle OnCompleted ou onError sur la souscription enfant.

En termes simples, si vous souhaitez propager l’exception, vous devez rediffuser l’exception initiale une fois que vous avez suffisamment réessayé. 

Une méthode simple consiste à définir votre Observable.range pour qu’il soit supérieur de 1 au nombre de fois que vous souhaitez réessayer. 

Dans votre fonction Zip, testez ensuite le nombre actuel de tentatives. S'il est égal à NUMBER_OF_RETRIES + 1, retournez Observable.error(throwable) ou relancez votre exception.

PAR EXEMPLE

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);

De plus, doOnError n'affecte en aucune manière l'observable - il vous fournit simplement un point d'accroche pour effectuer une action en cas d'erreur. Un exemple courant est la journalisation.

7
Will

Une option consiste à utiliser Observable.materialize() pour convertir les éléments Observable.range() en notifications. Puis, une fois que onCompleted() est émis, on peut propager une erreur en aval (dans l'exemple ci-dessous, Pair est utilisé pour emballer les notifications Observable.range() et l'exception de 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;
        }
    }
1
m.ostroverkhov

Vous pouvez obtenir le comportement souhaité à l'aide du générateur RetryWhen dans rxjava-extras qui se trouve sur Maven Central. Utilisez la dernière version.

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);
0
Dave Moten