传递给 CompletableFuture.exceptionally() 的异常处理程序是否必须 return 一个有意义的值?

Does an exception handler passed to CompletableFuture.exceptionally() have to return a meaningful value?

我习惯了 ListenableFuture 模式,使用 onSuccess()onFailure() 回调,例如

ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
ListenableFuture<String> future = service.submit(...)
Futures.addCallback(future, new FutureCallback<String>() {
  public void onSuccess(String result) {
    handleResult(result);
  }
  public void onFailure(Throwable t) {
    log.error("Unexpected error", t);
  }
})

似乎 Java 8 的 CompletableFuture 旨在处理或多或少相同的用例。天真地,我可以开始将上面的示例翻译为:

CompletableFuture<String> future = CompletableFuture<String>.supplyAsync(...)
  .thenAccept(this::handleResult)
  .exceptionally((t) -> log.error("Unexpected error", t));

这肯定比 ListenableFuture 版本更简洁,看起来很有前途。

但是,它不会编译,因为 exceptionally() 不采用 Consumer<Throwable>,它采用 Function<Throwable, ? extends T> —— 在这种情况下,Function<Throwable, ? extends String> .

这意味着我不能只记录错误,我必须在错误情况下为return想出一个String值,并且没有任何意义String 错误情况下的值为 return。我可以 return null,只是为了让代码编译:

  .exceptionally((t) -> {
    log.error("Unexpected error", t);
    return null; // hope this is ignored
  });

但这又开始变得冗长了,除了冗长之外,我不喜欢让 null 到处飘荡——这表明有人可能会尝试检索或捕获该值,并且在很久以后的某个时候,我可能会有意想不到的 NullPointerException.

如果 exceptionally() 拿了一个 Function<Throwable, Supplier<T>> 我至少可以做这样的事 --

  .exceptionally((t) -> {
    log.error("Unexpected error", t);
    return () -> { 
      throw new IllegalStateException("why are you invoking this?");
    }
  });

-- 但事实并非如此。

exceptionally() 永远不会产生有效值时,正确的做法是什么?我可以用 CompletableFuture 做些什么,或者在新的 Java 8 个库中做些什么,更好地支持这个用例?

请注意,exceptionally(Function<Throwable,? extends T> fn) 也是 returns CompletableFuture<T>。所以你可以进一步链接。

Function<Throwable,? extends T> 的 return 值旨在为下一个链接方法生成回退结果。因此,如果数据库不可用,您可以从缓存中获取值。

CompletableFuture<String> future = CompletableFuture<String>.supplyAsync(/*get from DB*/)
  .exceptionally((t) -> {
    log.error("Unexpected error", t);
    return "Fallback value from cache";
  })
  .thenAccept(this::handleResult);

如果 exceptionally 接受 Consumer<T> 而不是函数,那么它如何 return 一个 CompletableFuture<String> 来进一步链接?

我想你想要 exceptionally 的变体,它会 return void。但不幸的是,不,没有这样的变体。

因此,在您的情况下,您可以安全地 return 来自此后备函数的任何值,如果您不 return 此 future 对象并且不在您的代码中进一步使用它(因此它不能被进一步链接)。最好不要将它分配给变量。

CompletableFuture<String>.supplyAsync(/*get from DB*/)
  .thenAccept(this::handleResult)
  .exceptionally((t) -> {
    log.error("Unexpected error", t);
    return null;
  });

CompletableFuture的正确对应变换是:

CompletableFuture<String> future = CompletableFuture.supplyAsync(...);
future.thenAccept(this::handleResult);
future.exceptionally(t -> {
    log.error("Unexpected error", t);
    return null;
});

另一种方式:

CompletableFuture<String> future = CompletableFuture.supplyAsync(...);
future
    .whenComplete((r, t) -> {
        if (t != null) {
            log.error("Unexpected error", t);
        }
        else {
            this.handleResult(r);
        }
    });

这里有趣的部分是您在示例中链接了 futures。看似流畅的语法实际上是链式的futures,不过这里好像你不想要那样。

如果您想 return 一个处理具有内部未来结果的事物的未来,return 由 whenComplete 编辑的未来可能会很有趣。它保留当前未来的异常,如果有的话。但是,如果 future 正常完成并且继续抛出异常,它将异常完成并抛出异常。

不同之处在于,future 完成后发生的任何事情都将在下一次继续之前发生。如果您是 future 的最终用户,则使用 exceptionallythenAccept 是等价的,但是如果您向调用者提供未来返回,则任何一个都将在没有完成的情况下进行处理通知(如果可能的话,就像在后台一样),很可能是 exceptionally 延续,因为您可能希望异常在进一步的延续上级联。