如果重试时出现捕获错误:重试耗尽

时间:2016-06-16 13:33:49

标签: rx-java reactive-programming

documentation for 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)子句之后添加retryWhen 不会发现错误。它甚至被排出了吗?

重试之前添加.doOnError(System.out::println) 当显示always fails所有重试时。

7 个答案:

答案 0 :(得分:13)

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

答案 1 :(得分:7)

retryWhen Javadoc表示:

  

如果Observable调用onComplete或onError,则重试将调用   子订阅上的onCompleted或onError。

简单地说,如果你想传播异常,一旦你有足够的重试,你就需要重新抛出原始异常。

一种简单的方法是将Observable.range设置为大于您要重试的次数。

然后在你的zip函数中测试当前的重试次数。如果它等于NUMBER_OF_RETRIES + 1,则返回Observable.error(throwable)或重新抛出异常。

EG

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 - 它只是为您提供了一个钩子,以便在发生错误时执行某些操作。一个常见的例子是记录。

答案 2 :(得分: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;
        }
    }

答案 3 :(得分:0)

您可以使用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);

答案 4 :(得分:0)

这是我发现在 Kotlin 中实现它的最简单方法。重试 maxRetries 次,如果没有成功尝试,则将错误传递给 onErrorResumeNext 的源。

api.getItems()
        .retryWhen { errors ->
            errors.zipWith(Observable.range(1, maxRetries + 1), { error, i ->
                if (i <= maxRetries) {
                    Observable.timer(i.toLong(), TimeUnit.SECONDS)
                } else {
                    throw error
                }
            })
        }.onErrorResumeNext { error ->
            return@onErrorResumeNext Observable.error(factory.create(error))
        }

答案 5 :(得分:-1)

重试后需要使用 onErrorResumeNext

在你的例子中

    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), (n, i) -> {
            if (i == NUMBER_OF_RETRIES + 1) {
                throw Throwables.propagate(n);
            }
            else {
                return i;
            }
        }).flatMap(i -> {
            System.out.println("delay retry by " + i + " second(s)");
            return Observable.timer(i, TimeUnit.SECONDS);
        });
    })
    .onErrorResumeNext(t -> {System.out.println("Error after all retries:" + t.getMessage());
                                              return Observable.error(t);
                                          })
    .toBlocking().forEach(System.out::println);

在本课程的最底部,您可以看到一个实际的例子来了解它的工作原理。 https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/errors/ObservableExceptions.java

答案 6 :(得分:-1)

你可以使用scan function,它返回一个带有累积索引的对,并决定是否传递错误:

.retryWhen(attempts -> 
    return .scan(Pair.create(0, null), (index, value) -> Pair.create(index.first + 1, value))
            .flatMap(pair -> {
                if(pair.first > MAX_RETRY_COUNT) {
                    throw new RuntimeException(pair.second);
                }
                return Observable.timer(pair.first, TimeUnit.SECONDS);
            });

或者您可以坚持使用zipWith运算符但增加range Observable中的数字并返回一对,而不是单独使用索引。这样,您就不会丢失有关之前throwable的信息。

attempts
    .zipWith(Observable.range(1, MAX_RETRY_COUNT + 1), (throwable, i) -> Pair.create(i, throwable))
    .flatMap(pair -> {
        if(pair.first > MAX_RETRY_COUNT) throw new RuntimeException(pair.second);
        System.out.println("delay retry by " + pair.first + " second(s)");
        return Observable.timer(pair.first, TimeUnit.SECONDS);
    });