I'm receiving responses from a service call via CompletableFuture. I'd like to handle some known exceptions the service returns — such as optimistic concurrency control conflicts.
Here's what I've got. Is there a better way to do this which does not wrap the exceptions or use SneakyThrows? Wrapping exceptions would mean other exception handlers must inspect causal chains instead of merely using instanceof
.
someService.call(request)
.handle((response, error) -> {
if (error == null)
return CompletableFuture.completedFuture(response);
else if (error instanceof OCCException)
return CompletableFuture.completedFuture(makeDefaultResponse());
CompletableFuture<Response> errorFuture = new CompletableFuture<>();
errorFuture.completeExceptionally(error);
return errorFuture;
}).thenCompose(Function.identity());
Along the same vein, is there a way to replicated guava's withFallback without the wrap-unwrap?
CompletableFuture<T> withFallback(CompletableFuture<T> future,
Function<Throwable, ? extends CompletableFuture<T>> fallback) {
return future.handle((response, error) -> {
if (error == null)
return CompletableFuture.completedFuture(response);
else
return fallback.apply(error);
}).thenCompose(Function.identity());
}
...
// Here's the first part of the question implemented using withFallback.
// It's a little cleaner, but it still requires wrapping as a future.
withFallback(someService.call(request), error -> {
if (error instanceof OCCException)
return CompletableFuture.completedFuture(makeDefaultResponse());
CompletableFuture<Response> errorFuture = new CompletableFuture<>();
errorFuture.completeExceptionally(error);
return errorFuture;
});
For completeness, here is what it would look like if I allowed the exceptions to be wrapped. (I've got a unit test to verify the thrown exception propagates down the chain):
someService.call(request)
.exceptionally(error -> {
if (error instanceof OCCException)
return makeDefaultResponse();
else
// wrap because error is declared as a checked exception
throw new RuntimeException(error);
});
The guava style function you asked for could be implemented like this:
static <T> CompletableFuture<T> withFallback(CompletableFuture<T> future,
Function<Throwable, ? extends CompletableFuture<T>> fallback) {
return future.handle((response, error) -> error)
.thenCompose(error -> error!=null? fallback.apply(error): future);
}
being both, more compact and saving resources by reusing the source future for the case we don’t want to do any transformations. But to give the caller the opportunity to do the same without the introduction of another helper method, it would be very useful to change the method and use a BiFunction
which gets the source future as additional parameter:
static <T> CompletableFuture<T> withFallback(CompletableFuture<T> future,
BiFunction<CompletableFuture<T>, Throwable, ? extends CompletableFuture<T>>
fallback) {
return future.handle((response, error) -> error)
.thenCompose(error -> error!=null? fallback.apply(future,error): future);
}
then you can use it like this:
withFallback(someService.call(request), (f,t) -> t instanceof OCCException?
CompletableFuture.completedFuture(makeDefaultResponse()): f)
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