简体   繁体   中英

CompletableFuture compose for exceptions

The CompletableFuture API allows us to use thenCompose to chain another future:

CompletableFuture<String> future1 = submit("foo");
CompletableFuture<String> future2 = future.thenCompose((result) -> submit(result));

This only works for successful responses though. Is there a way to do the same but also including exception handling?

For example:

CompletableFuture<String> future1 = submit("foo");
CompletableFuture<String> future2 = future.handleCompose((result, error) -> {
  if (error != null)
    return submit("failure"); // Handle error by doing a different action with the same result (like a fallback)
  else
    return submit(result);
});

I know you can do something like whenComplete :

CompletableFuture<String> future2 = new CompletableFuture<>();
CompletableFuture<String> future1 = submit("foo");
future.whenComplete((result, error) -> {
  CompletableFuture<String> tmp;
  if (error != null)
    tmp = submit("failure");
  else
    tmp = submit(result);
  tmp.whenComplete((result2, error2) -> {
    if (error2 != null) future2.completeExceptionally(error2);
    else future2.complete(result2);
  });
});

This however loses the ability to cancel properly and seems like a very "hacky" solution compared to the success composition handling. Is there a good way that does not require making my own extension of the CompletableFuture class?

The problem is that I need to return a single future from my method and it should be capable to cancel the entire process at any point in time.

You can combine an handle() returning a CompletableFuture with a thenCompose(x -> x) :

CompletableFuture<String> future2 = future.handle((result, error) -> {
  if (error != null)
    return submit("failure"); // Handle error by doing a different action with the same result (like a fallback)
  else
    return submit(result);
})
.thenCompose(x -> x);

Unfortunately, there is no way to avoid that last thenCompose() . See also How to avoid invoking CompletableFuture.thenCompose(x -> x) ? and my comment on it.

You can make use of CompletableFuture.allOf(..), This is not the only solution but it is better.

public CompletableFuture<String> getSomeDataFromExternalIfPossible() {
    var input = "SomeKey";
    CompletableFuture<String> externalDataFetchCFuture = getDataFromExternal(input);
    return CompletableFuture.allOf(externalDataFetchCFuture)
        .exceptionally(
            ex -> {
              // This exceptionally(...) is required as allOf(...) propagates exception thrown by any of the supplied futures
              // Log if you are interested in failed case of externalDataFetchCFuture
              return null;
            })
        .thenCompose(
            unused -> {
              // Check if externalDataFetchCFuture completed successfully and invoke other Async method if not
              if (!externalDataFetchCFuture.isCompletedExceptionally()) {
                // On externalDataFetchCFuture successful execution
                return externalDataFetchCFuture;
              } else {
                // On externalDataFetchCFuture failed execution
                // Fetching data from DB or from any other integration
                return getDataFromExternal2(input);
              }
            });
}

private CompletableFuture<String> getDataFromExternal(String input) {
  return CompletableFuture.completedFuture("abc");
}

private CompletableFuture<String> getDataFromExternal2(String input) {
  return CompletableFuture.completedFuture("xyz");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM