考虑以下示例:
Observable.range(1, 10).subscribe(i -> {
System.out.println(i);
if (i == 5) {
throw new RuntimeException("oops!");
}
}, Throwable::printStackTrace);
这将输出1到5之间的数字,然后打印异常。
我想要实现的是让观察者保持订阅并在抛出异常后继续运行,即打印从1到10的所有数字。
我尝试过使用retry()
和other various error handling operators,但正如文档中所述,它们的目的是处理observable本身发出的错误。
最直接的解决方案就是将整个onNext
包裹到try-catch块中,但这对我来说听起来不是一个好方法。在similar Rx.NET question中,建议的解决方案是创建一个扩展方法,通过创建代理可观察来进行包装。我试图重拍它:
Observable<Integer> origin = Observable.range(1, 10);
Observable<Integer> proxy = Observable.create((Observable.OnSubscribe<Integer>) s ->
origin.subscribe(i -> {try { s.onNext(i); } catch (Exception ignored) {}}, s::onError, s::onCompleted));
proxy.subscribe(i -> {
System.out.println(i);
if (i == 5) {
throw new RuntimeException("oops!");
}
}, Throwable::printStackTrace);
这不会改变任何东西,因为RxJava本身将订阅者包装成SafeSubscriber
。使用unsafeSubscribe
解决它并不是一个好的解决方案。
我该怎么做才能解决这个问题?
答案 0 :(得分:17)
这是学习Rx时常见的问题。
您建议将异常处理逻辑放在订阅者中,而不是创建通用的可观察包装器。
请记住,Rx是将事件推送给订阅者。
从可观察的界面中可以清楚地看到,除了他们处理事件所花费的时间或任何其他信息之外,观察者所知道的任何用户都不知道。抛出异常。
处理订阅者异常并继续向该订阅者发送事件的通用包装器是个坏主意。
为什么呢?那么observable应该只知道订户现在处于未知的故障状态。在这种情况下继续发送事件是不明智的 - 也许,例如,订阅者处于这样一种状态,即从这一点开始的每个事件都会抛出异常并花费一些时间来完成它。
一旦订阅者抛出异常,对于可观察者只有两种可行的行动方案:
订户异常的具体处理将是一个糟糕的设计选择;它会在订阅者和可观察者之间产生不适当的行为耦合。因此,如果你想要对不好的订阅者有弹性,那么上面的两个选择实际上是观察者自身的合理责任限制。
如果您希望订户具有弹性并继续运行,那么您应该将其包装在异常处理逻辑中,以处理特定异常您知道如何恢复从(并可能处理瞬态异常,记录,重试逻辑,断路等)。
只有订阅者本身才会有上下文来了解它是否适合在失败时接收更多事件。
如果您的情况需要开发可重复使用的错误处理逻辑,请将自己置于包装观察者事件处理程序而不是 observable 的思维模式中,并注意不要盲目地进行传输面对失败的事件。 Release It!虽然没有关于Rx的文章,但是有趣的软件工程经典在这最后一点上有很多话要说。如果您还没有阅读,我强烈建议。