我正在使用CompletableFuture链并偶然发现一个具有意外行为的情况(对我来说至少):如果在.thenCompose()
调用中传递了一个例外的CompletableFuture,则生成的CompletableFuture将以原始异常包装完成在CompletionException
。如果没有例子,可能很难理解:
public static <T> CompletableFuture<T> exceptional(Throwable error) {
CompletableFuture<T> future = new CompletableFuture<>();
future.completeExceptionally(error);
return future;
}
public static void main(String[] args) {
CompletableFuture<Void> exceptional = exceptional(new RuntimeException());
exceptional
.handle((result, throwable) -> {
System.out.println(throwable);
// java.lang.RuntimeException
System.out.println(throwable.getCause());
// null
return null;
});
CompletableFuture
.completedFuture(null)
.thenCompose(v -> exceptional)
.handle((result, throwable) -> {
System.out.println(throwable);
// java.util.concurrent.CompletionException: java.lang.RuntimeException
System.out.println(throwable.getCause());
// java.lang.RuntimeException
return null;
});
}
当然,无论链条之前或之后有多少次转换,我都希望能够处理相同的RuntimeException
。我有两个问题:
答案 0 :(得分:2)
thenCompose()
的JavaDoc是:
返回一个新的CompletionStage,当此阶段正常完成时,将使用此阶段作为所提供函数的参数执行。有关特殊完成的规则,请参阅
CompletionStage
文档。
和接口的定义说明:
[...]在所有其他情况下,如果一个阶段的计算突然以(未经检查的)异常或错误终止,那么所有需要完成的依赖阶段也会异常完成,
CompletionException
将异常保持为原因。 [...]
当thenCompose
返回依赖阶段时,这是预期的行为。
事实上,除CompletionException
之外,您可以使用CompletableFuture
,completeExceptionally()
等方法明确完成cancel()
的情况。即使像supplyAsync()
这样的方法也会包含您的异常。
我认为没有任何其他选项可以访问原始异常,因为用getCause()
解包它已经非常容易了。如果你真的需要经常这样做,你可以写一个帮助方法,如:
public static <T, U> BiFunction<? super T, Throwable, ? extends U>
unwrappingCompletionException(BiFunction<? super T, Throwable, ? extends U> fn) {
return (t, u) -> {
if (u instanceof CompletionException) {
return fn.apply(t, u.getCause());
}
return fn.apply(t, u);
};
}
并按如下方式使用:
CompletableFuture
.completedFuture(null)
.thenCompose(v -> exceptional)
.handle(unwrappingCompletionException((result, throwable) -> {
[…]
}));