I'm used to the ListenableFuture
pattern, with onSuccess()
and onFailure()
callbacks, e.g.
ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
ListenableFuture<String> future = service.submit(...)
Futures.addCallback(future, new FutureCallback<String>() {
public void onSuccess(String result) {
handleResult(result);
}
public void onFailure(Throwable t) {
log.error("Unexpected error", t);
}
})
It seems like Java 8's CompletableFuture
is meant to handle more or less the same use case. Naively, I could start to translate the above example as:
CompletableFuture<String> future = CompletableFuture<String>.supplyAsync(...)
.thenAccept(this::handleResult)
.exceptionally((t) -> log.error("Unexpected error", t));
This is certainly less verbose than the ListenableFuture
version and looks very promising.
However, it doesn't compile, because exceptionally()
doesn't take a Consumer<Throwable>
, it takes a Function<Throwable, ? extends T>
-- in this case, a Function<Throwable, ? extends String>
.
This means that I can't just log the error, I have to come up with a String
value to return in the error case, and there is no meaningful String
value to return in the error case. I can return null
, just to get the code to compile:
.exceptionally((t) -> {
log.error("Unexpected error", t);
return null; // hope this is ignored
});
But this is starting to get verbose again, and beyond verbosity, I don't like having that null
floating around -- it suggests that someone might try to retrieve or capture that value, and that at some point much later I might have an unexpected NullPointerException
.
If exceptionally()
took a Function<Throwable, Supplier<T>>
I could at least do something like this --
.exceptionally((t) -> {
log.error("Unexpected error", t);
return () -> {
throw new IllegalStateException("why are you invoking this?");
}
});
-- but it doesn't.
What's the right thing to do when exceptionally()
should never produce a valid value? Is there something else I can do with CompletableFuture
, or something else in the new Java 8 libraries, that better supports this use case?
Using exceptionally() methodThis method returns a new CompletionStage that, when this stage completes with exception, is executed with this stage's exception as the argument to the supplied function. Otherwise, if this stage completes normally, then the returned stage also completes normally with the same value.
The CompletableFuture. join() method is similar to the get method, but it throws an unchecked exception in case the Future does not complete normally. This makes it possible to use it as a method reference in the Stream.
Since Void can not be instantiated, you can only complete a CompletableFuture<Void> with a null result, which is exactly what you also will get when calling join() on the future returned by allOf() once it has been successfully completed. CompletableFuture<Void> cf = CompletableFuture.
runAsync(() -> { //process and throw exception }, anInstanceOfTaskExecutor ) . thenRun(() -> {}) . exceptionally(exception -> { // do something, handle exception }) )); In this case, it will execute thenRun .
A correct corresponding transformation with CompletableFuture
is:
CompletableFuture<String> future = CompletableFuture.supplyAsync(...);
future.thenAccept(this::handleResult);
future.exceptionally(t -> {
log.error("Unexpected error", t);
return null;
});
Another way:
CompletableFuture<String> future = CompletableFuture.supplyAsync(...);
future
.whenComplete((r, t) -> {
if (t != null) {
log.error("Unexpected error", t);
}
else {
this.handleResult(r);
}
});
The interesting part here is that you were chaining futures in your examples. The seemingly fluent syntax is actually chaining futures, but it seems you don't want that here.
The future returned by whenComplete
might be interesting if you want to return a future that processes something with an internal future's outcome. It preserves the current future's exception, if any. However, if the future completed normally and the continuation throws, it'll complete exceptionally with the thrown exception.
The difference is that anything that happens after future
completes will happen before the next continuation. Using exceptionally
and thenAccept
is equivalent if you're the future
's end-user, but if you're providing a future back to a caller, either one will process without a completion notification (as if in the background, if you may), most probably the exceptionally
continuation since you'll probably want the exception to cascade on further continuations.
Note, that exceptionally(Function<Throwable,? extends T> fn)
also returns CompletableFuture<T>
. So you can chain futher.
The return value of Function<Throwable,? extends T>
is meant to produce fallback result for next chained methods. So you can for example get the value from Cache if it is unavailable from DB.
CompletableFuture<String> future = CompletableFuture<String>.supplyAsync(/*get from DB*/)
.exceptionally((t) -> {
log.error("Unexpected error", t);
return "Fallback value from cache";
})
.thenAccept(this::handleResult);
If exceptionally
would accept Consumer<T>
instead of function, then how it could return a CompletableFuture<String>
for chaining futher?
I think you want a variant of exceptionally
which would return void
. But unfortunately, no, there is no such variant.
So, in your case you can safely return any value from this fallback function, if you not return this future
object and not use it futher in your code (so it can't be chained futher). Better not even assign it to a variable.
CompletableFuture<String>.supplyAsync(/*get from DB*/)
.thenAccept(this::handleResult)
.exceptionally((t) -> {
log.error("Unexpected error", t);
return null;
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With