For your callable as written, you could simply use CompletableFuture.supplyAsync(() -> 0d);
.
If, however, you have an existing Callable
, using it with CompletableFuture
is not so straight-forward due to the checked exceptions that a callable might throw.
You may use an ad-hoc Supplier
which catches exceptions and re-throws it wrapped in an unchecked exception like
CompletableFuture.supplyAsync(() -> {
try { return callable.call(); }
catch(Exception e) { throw new CompletionException(e); }
})
Using the specific type CompletionException
instead of an arbitrary subtype of RuntimeException
avoids getting a CompletionException
wrapping a runtime exception wrapping the actual exception when calling join()
.
Still, you’ll notice the wrapping when chaining an exception handler to the CompletableFuture
. Also, the CompletionException
thrown by join()
will be the one created in the catch
clause, hence contain the stack trace of some background thread rather than the thread calling join()
. In other words, the behavior still differs from a Supplier
that throws an exception.
Using the slightly more complicated
public static <R> CompletableFuture<R> callAsync(Callable<R> callable) {
CompletableFuture<R> cf = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
try { cf.complete(callable.call()); }
catch(Throwable ex) { cf.completeExceptionally(ex); }
});
return cf;
}
you get a CompletableFuture
which behaves exactly like supplyAsync
, without additional wrapper exception types, i.e. if you use
callAsync(task).exceptionally(t -> {
t.printStackTrace();
return 42.0;
})
t
will be the exact exception thrown by the Callable
, if any, even if it is a checked exception. Also callAsync(task).join()
would produce a CompletionException
with a stack trace of the caller of join()
directly wrapping the exception thrown by the Callable
in the exceptional case, exactly like with the Supplier
or like with runAsync
.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…