强制执行所需的 CompletableFuture 行为
Enforcing desired CompletableFuture behavior
我正在玩 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
,无论链中之前或之后有多少转换。我有两个问题:
- 这是预期的行为吗?
- 除了手动解包之外,我是否有任何选项来保持原始异常通过?
thenCompose()
的 JavaDoc 是:
Returns a new CompletionStage that, when this stage completes normally, is executed with this stage as the argument to the supplied function. See the CompletionStage
documentation for rules covering exceptional completion.
以及接口状态的定义:
[…] In all other cases, if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException
holding the exception as its cause. […]
作为 thenCompose
returns 依赖阶段,这是预期行为。
事实上,除了 CompletionException
之外的唯一情况是当您使用 completeExceptionally()
、cancel()
等方法显式完成 CompletableFuture
时等。甚至 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) -> {
[…]
}));
我正在玩 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
,无论链中之前或之后有多少转换。我有两个问题:
- 这是预期的行为吗?
- 除了手动解包之外,我是否有任何选项来保持原始异常通过?
thenCompose()
的 JavaDoc 是:
Returns a new CompletionStage that, when this stage completes normally, is executed with this stage as the argument to the supplied function. See the
CompletionStage
documentation for rules covering exceptional completion.
以及接口状态的定义:
[…] In all other cases, if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a
CompletionException
holding the exception as its cause. […]
作为 thenCompose
returns 依赖阶段,这是预期行为。
事实上,除了 CompletionException
之外的唯一情况是当您使用 completeExceptionally()
、cancel()
等方法显式完成 CompletableFuture
时等。甚至 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) -> {
[…]
}));