CompletableFuture 为异常撰写

CompletableFuture compose for exceptions

CompletableFuture API 允许我们使用 thenCompose 链接另一个未来:

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

不过这只适用于成功的回复。有没有办法做到同样的同时还包括异常处理?

例如:

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);
});

我知道你可以做类似 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);
  });
});

然而,这失去了正确取消的能力,与成功的组合处理相比,这似乎是一个非常 "hacky" 的解决方案。有没有不需要自己扩展 CompletableFuture 的好方法 class?

问题是我需要从我的方法中 return 一个未来,它应该能够在任何时间点取消整个过程。

您可以组合返回 CompletableFuturehandle()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);

不幸的是,没有办法避免最后的 thenCompose()。另见 How to avoid invoking CompletableFuture.thenCompose(x -> x)? 和我对此的评论。

编辑:对于 Java 12+,现在有 exceptionallyCompose(),我将其描述为

您可以使用CompletableFuture.allOf(..),这不是唯一的解决方案,但更好。

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");
}