Say I have an API that, based on some query criteria, will find or construct a widget:
Widget getMatchingWidget(WidgetCriteria c) throws Throwable
The (synchronous) client code looks like:
try {
Widget w = getMatchingWidget(criteria);
processWidget(w);
} catch (Throwable t) {
handleError(t);
}
Now say finding or constructing a widget is unpredictably expensive, and I don't want clients to block while waiting for it. So I change it to:
CompletableFuture<Widget> getMatchingWidget(WidgetCriteria c)
Clients can then write either:
CompletableFuture<Widget> f = getMatchingWidget(criteria);
f.thenAccept(this::processWidget)
f.exceptionally(t -> { handleError(t); return null; })
or:
getMatchingWidget(criteria).whenComplete((t, w) -> {
if (t != null) { handleError(t); }
else { processWidget(t); }
});
Now, let's say instead the synchronous API can return 0 to n widgets:
Stream<Widget> getMatchingWidgets(WidgetCriteria c)
Naively, I could write:
CompletableFuture<Stream<Widget>> getMatchingWidgets(WidgetCriteria c)
However, this doesn't actually make the code non-blocking, it just pushes the blocking around -- either the Future
blocks until all the Widgets
are available, or the code that iterates over the Stream
blocks waiting for each Widget
. What I want is something that will let me process each widget as they arrive, something like:
void forEachMatchingWidget(WidgetCriteria c, Consumer<Widget> widgetProcessor)
But this doesn't offer error handling, and even if I add an additional Consumer<Throwable> errorHandler
, it doesn't let me, for instance, compose my widget retrieval with other queries, or transform the results.
So I'm looking for some composable thing that combines the characteristics of a Stream
(iterability, transformability) with the characteristics of a CompletableFuture
(asynchronous result and error handling). (And, while we're at it, back pressure might be nice.)
Is this a java.util.concurrent.Flow.Publisher? An io.reactivex.Observable? Something more complicated? Something simpler?