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

Dav*_*les 17 java completable-future

我习惯了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);
  }
})
Run Code Online (Sandbox Code Playgroud)

看起来Java 8的CompletableFuture目的是处理或多或少相同的用例.天真的,我可以开始将上面的例子翻译为:

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

这肯定不如ListenableFuture版本那么冗长,看起来非常有前景.

但是,它没有编译,因为exceptionally()不需要a Consumer<Throwable>,它需要一个Function<Throwable, ? extends T>- 在这种情况下,a Function<Throwable, ? extends String>.

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

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

但是这又开始变得冗长,而且除了冗长之外,我不喜欢null让它漂浮在周围 - 这表明有人可能会尝试检索或捕获该值,并且在某些时候我可能会出现意外情况NullPointerException.

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

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

- 但事实并非如此.

什么是exceptionally()永远不应该产生有效价值的事情?我可以用CompletableFuture新的Java 8库中的其他东西做些什么来更好地支持这个用例吗?

ace*_*ent 12

正确的相应转换CompletableFuture是:

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

其他方式:

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

这里有趣的部分是你在你的例子中链接了未来.看似流畅的语法实际上是链接未来,但似乎你不想在这里.

通过返回的未来whenComplete,如果你想返回未来处理与内部未来的结果的话会很有趣.如果有的话,它会保留当前未来的例外情况.但是,如果将来正常完成并且继续抛出,则抛出异常将完全异常完成.

不同之处在于future完成后发生的任何事情都会在下一次延续之前发生.如果您是最终用户,则使用exceptionallythenAccept等效future,但如果您将未来提供给呼叫者,则任何人都将在没有完成通知的情况下处理(如果在后台,如果可能的话),最有可能在exceptionally延续,因为你可能会想例外级联的进一步延续.

  • 不幸的是,第一个实际上并没有编译,因为`log.error(...)`是`void`,而`exceptionally()`希望返回将来返回类型的东西(在这种情况下是`String`)。(这从根本上导致了我的问题。)不过,感谢您对流畅的API和链接的评论。 (2认同)

Rus*_*nko 7

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

的返回值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);
Run Code Online (Sandbox Code Playgroud)

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

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

因此,在您的情况下,如果您不返回该future对象并且不在代码中进一步使用它(因此无法进一步链接它),您可以安全地从此后备函数返回任何值。最好甚至不要将其分配给变量。

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