我正在使用io.projectreactor 3(reactor-core 3.2.6.RELEASE),并且注意到有关错误处理的一些差异。不幸的是,官方文档没有提供足够的详细信息来解决我的问题。
我有以下4个摘要。在某些情况下,异常将被忽略,而在其他情况下,它将进一步抛出。实际产生和使用异常的方式是什么?
代码段1
在这种情况下,异常将被忽略,并且main()将在不接收异常的情况下完成。
import reactor.core.publisher.Flux;
class Scratch {
public static void main(String[] args) throws Throwable {
Flux.push(sink -> {
sink.next(1);
sink.next(2);
}).doOnNext(e -> {
throw new RuntimeException("HELLO WORLD");
}).subscribe(System.out::println, e -> {
throw new RuntimeException(e);
});
System.out.println("DONE");
}
}
输出:
DONE
代码段2
与上面的示例相似,除了我们不使用Flux.push而是使用Flux.just。 Main()将收到异常。
import reactor.core.publisher.Flux;
class Scratch {
public static void main(String[] args) throws Throwable {
Flux.just(
1
).doOnNext(e -> {
throw new RuntimeException("HELLO WORLD");
}).subscribe(System.out::println, e -> {
throw new RuntimeException(e);
});
System.out.println("DONE");
}
}
输出:
Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: HELLO WORLD
at Scratch.lambda$main$1(scratch_15.java:10)
...
代码段3
我们通过调用sink.error发出异常信号。 Main()将不会收到异常。
import reactor.core.publisher.Flux;
class Scratch {
public static void main(String[] args) throws Throwable {
Flux.push(sink -> {
sink.next(1);
sink.next(2);
sink.error(new RuntimeException("HELLO WORLD"));
}).subscribe(System.out::println, e -> {
throw new RuntimeException(e);
});
System.out.println("DONE");
}
}
输出:
1
2
DONE
代码段4
我们直接抛出异常。 Main()将收到异常。
import reactor.core.publisher.Flux;
class Scratch {
public static void main(String[] args) throws Throwable {
Flux.push(sink -> {
sink.next(1);
sink.next(2);
throw new RuntimeException("HELLO WORLD");
}).subscribe(System.out::println, e -> {
throw new RuntimeException(e);
});
System.out.println("DONE");
}
}
输出
1
2
Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: HELLO WORLD
at Scratch.lambda$main$1(scratch_15.java:10)
...
使用反应堆内核时,处理异常的正确方法是什么?唯一可靠的方法似乎根本不使用错误回调,而是用try / catch包围flux.subscribe。但是在那种情况下,我总是收到UnsupportedOperationException
而不是原始异常,然后我需要使用Exceptions.isErrorCallbackNotImplemented
来检查它是否来自响应式,提取嵌套的异常然后将其抛出。
这当然可以完成,但是需要在我们使用Flux的每个地方进行一致的订阅。对我来说那不太好。我在这里想念什么?
答案 0 :(得分:2)
在所有示例中,问题都是从.subscribe(...)
错误处理lambda引发的。
如果要在主块中引发异常,请使用block()
变体。
如果要测试错误是否在整个管道中传播,请使用StepVerifier.create(pipeline).expectError(...).verify()
。
.subscribe
通常是为了获得“终端”状态的通知,而不是旨在恢复或重新抛出错误(为此使用上游的onError*
运算符)。
基于just
的示例似乎正确传播了该异常,因为它们在订阅时不执行用户提供的代码,因此在subscribe(Consumer<Throwable>)
期间没有进行try / catch。
push
,例如generate
/ create
/ defer
和compose
,在以下位置执行一些用户定义的逻辑(Consumer<FluxSink>
)订阅。他们防范整个Consumer
抛出异常,并尝试传播(作为onError
信号),而不是直接抛出该异常。
但是,如果Consumer
的失败是在执行sink
的一种方法时引起的,那么如果subscriber
重新抛出,则可能会出现问题:我们在以下位置输入一个递归调用接收器调用接收器。当检测到接收器的递回排水时,我们通过退出来防止这种无限情况。
这就是为什么在push
或sink.next
(示例1和3)中触发错误的基于sink.error
的示例在主程序中无法产生异常的原因:
Consumer
已应用sink.next
被调用,并且下一个运算符创建异常1,或者sink.error
被调用subscribe
,并重新抛出为例外2 Consumer.apply
短路,异常2传递给sink.error
另一方面,在示例4中,我们不再处于调用接收器方法的中间,并且原始异常不会首先到达订阅者:
Consumer
已应用Consumer.apply
短路,并且异常1传递给sink.error