Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CompletableFuture withFallback / handle only some errors

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);
    });
like image 372
Michael Deardeuff Avatar asked Aug 16 '14 08:08

Michael Deardeuff


1 Answers

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)
like image 94
Holger Avatar answered Oct 28 '22 01:10

Holger