I thought I understood whenComplete
but I'm not sure now. This question originated in another thread.
The way we work with futures in my company is by chaining them:
CompletionStage<Foo> getFoo() {
// ...
return barService.getBar()
.thenCompose(bar -> {
CompletionStage<Baz> baz = bazService.getBaz(bar);
// ...
return qux;
})
.thenApply(qux -> {
CompletionStage<Quux> quux = quuxService.getQuux(qux);
// ...
return foo;
});
}
qux and quux are apparently the metasyntactic variables that follow foo, bar, and baz.
Now let's say I wanted to send a confirmation email when foo
has been gotten. I don't need the sending of this confirmation email to hold up the response to whatever client called getFoo
. We use whenComplete
for these scenarios:
CompletionStage<Foo> getFoo() {
// ...
return barService.getBar()
.thenCompose(bar -> {
CompletionStage<Baz> baz = bazService.getBaz(bar);
// ...
return qux;
})
.thenApply(qux -> {
CompletionStage<Quux> quux = quuxService.getQuux(qux);
// ...
return foo;
}) _
.whenComplete((foo, ex) -> {. |
if (ex == null) { |
emailService.sendEmail(foo); | (NEW)
} |
}); _|
}
Now I thought the action in whenComplete
happened in a separate thread completely independently of the thread it originated from. In other words, I thought as soon as foo
was found, it'd be on its way to the caller, no matter what happened inside the whenComplete
. But in reality, when the email service had a problem and threw an exception, the exception propagated all they way up, i.e. getFoo
threw an exception, even though foo
was found succesfully.
I was pointed to the Javadoc for whenComplete
, which indeed says:
Unlike method handle, this method is not designed to translate completion outcomes, so the supplied action should not throw an exception. However, if it does, the following rules apply: if this stage completed normally but the supplied action throws an exception, then the returned stage completes exceptionally with the supplied action's exception. Or, if this stage completed exceptionally and the supplied action throws an exception, then the returned stage completes exceptionally with this stage's exception.
So here's where I'm confused:
I thought the whole point of whenComplete
was to allow the originating thread to continue on its way without having to wait for the action in whenComplete
. If whether or not the chain will complete normally depends on the whenComplete
action though, doesn't that mean the chain always has to wait to see how whenComplete
completes? How is whenComplete
helping at all, if that's true?
I'm sure that I'm thinking of something wrong / misunderstanding how futures work, but I don't know what.
.whenComplete
is similar to a finally
block. It's useful when you want to do something even if the previous stage has failed. For example, log an exception or clean up some resources without stopping the propagation of the failure.
In your case, it seems that you want to send an email only when there are no failures. You could rewrite it as:
.thenAccept(emailService::sendEmail);
In this case if sendEmail
returns void, .thenAccept
will return when the method completes. But if sendEmail
returns a CompletionStage<Void>
, thenAccept
will continue without waiting for the result (and failure or successes will be ignored).
If you want to have control over the thread executors, you can use the .then*Async
methods instead of the regular ones. Otherwise, the pipeline will run everything in the same thread (if possible, it's not always the case).