执行所需的CompletableFuture行为

时间:2016-10-31 11:39:38

标签: java completable-future

我正在使用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。我有两个问题:

  • 这是预期的行为吗?
  • 除了手动展开外,我是否有任何选项可以保持原始异常通过?

1 个答案:

答案 0 :(得分:2)

thenCompose()的JavaDoc是:

  

返回一个新的CompletionStage,当此阶段正常完成时,将使用此阶段作为所提供函数的参数执行。有关特殊完成的规则​​,请参阅CompletionStage文档。

和接口的定义说明:

  

[...]在所有其他情况下,如果一个阶段的计算突然以(未经检查的)异常或错误终止,那么所有需要完成的依赖阶段也会异常完成,CompletionException将异常保持为原因。 [...]

thenCompose返回依赖阶段时,这是预期的行为

事实上,除CompletionException之外,您可以使用CompletableFuturecompleteExceptionally()等方法明确完成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) -> {
            […]
        }));