传递给ComppletableFuture.exceptionally()的异常处理程序是否必须返回有意义的值?

2022-09-02 01:47:30

我习惯了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的ComppletableFuture旨在处理或多或少相同的用例。天真地,我可以开始将上面的例子翻译为:

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>

这意味着我不能只记录错误,我必须想出一个值在错误情况下返回,并且在错误情况下没有有意义的值要返回。我可以返回,只是为了获取要编译的代码:StringStringnull

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

但是这又开始变得冗长了,除了冗长之外,我不喜欢让它漂浮在周围 - 这表明有人可能会试图检索或捕获该值,并且在某个时候我可能会遇到意想不到的事情 。nullNullPointerException

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

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

--但事实并非如此。

当永远不应该产生有效值时,正确的做法是什么?在新的 Java 8 库中,我还可以做一些其他事情来更好地支持这个用例吗?exceptionally()CompletableFuture


答案 1

正确的对应变换是: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);
        }
    });

这里有趣的部分是,你在例子中链上了期货。看似流畅的语法实际上是链接期货,但似乎你不希望在这里。

如果你想返回一个处理具有内部未来结果的东西的未来,那么返回的未来可能会很有趣。它保留了当前未来的异常(如果有)。但是,如果将来正常完成并且继续抛出,则它将异常完成并引发异常。whenComplete

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


答案 2

请注意,这也返回 .所以你可以进一步链条。exceptionally(Function<Throwable,? extends T> fn)CompletableFuture<T>

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

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

如果接受而不是函数,那么它如何返回一个用于进一步链接?exceptionallyConsumer<T>CompletableFuture<String>

我想你想要一个变体,它会返回。但不幸的是,没有,没有这样的变体。exceptionallyvoid

因此,在你的情况下,你可以安全地从这个回退函数返回任何值,如果你不返回这个对象,并且不在你的代码中进一步使用它(所以它不能被进一步链接)。最好不要将其分配给变量。future

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

推荐