This is a follow-up to Retry in Java futures. This post presented a snippet of code demonstrating how to compose futures in Java, specifically java.util.concurrent.CompletableFuture
. It demonstrated how to swallow exceptions and compose futures including branching etc.
It turns out that I need to interoperate with Guava futures too, specifically com.google.common.util.concurrent.ListenableFuture
. In some ways this post can be considered to be a short translation guide for switching back and forth between CompletableFuture
and ListenableFuture
.
On first encountering ListenableFuture
I was dismayed that, unlike CompletableFuture
, this interface does not expose any methods for composing futures. CompletableFuture
, for example, provides the following and many more:
exceptionally
thenCompose
thenApply
On deeper inspection, however, we find that Guava eschews interface methods for static helper methods on the com.google.common.util.concurrent.Futures
class. Roughly corresponding to the methods mentioned above we find:
catchingAsync
corresponding to exceptionally
transformAsync
corresponding to thenCompose
transform
corresponding to thenApply
My “add-with-retry” example using CompletableFuture
looks like:
These are my observations about this:
.exceptionally(e -> null)
null
returned from the App.add
future and a swallowed exceptionexceptionally
-followed-by-thenCompose
hackRegarding the union type comment above, I can think of at least one more principled approach we might take to handling exceptions that does not lose information. We could, for example, introduce a tagged union type as follows:
With the introduction of the tagged union, ValueOrException
(roughly equivalent to Haskell’s Either
), we can now distinguish accurately between the exceptional case and the null
response case.
Here’s my rough translation of this example into Guava futures:
And here’s the accompanying ListenableFutureHelper
helper class:
Here are my thoughts:
Futures
as opposed to first-class members of the ListenableFuture
interfaceListenableFuture
’s catchingAsync
is far superior to exceptionally
followed by thenCompose
and does not lose information and does not require the invention of a tagged union to distinguish between null
and exceptionsIn order to address the syntactic issues with ListenableFuture
, Guava now has com.google.common.util.concurrent.FluentFuture
in recent versions of the library. I haven’t had a chance to play with this yet. If I do, I’ll get back to you!
The latest and greatest version of this code can be found in the GitHub project.
Update: Here’s the FluentFuture
version
That’s better!
Update 2: Cancellation
You’ll notice that I sneakily introduced early cancellation into the FluentFuture
example. If you want to see how I added support for proper cancellation using all three future APIs, please consult the GitHub project.
Content © 2024 Richard Cook. All rights reserved.