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

时间:2016-06-12 03:45:48

标签: 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);
  }
})

似乎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>。< / p>

这意味着我不能只记录错误,我必须在错误情况下提出一个String值,并且没有有意义的String值返回错误案例。我可以返回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库中的其他东西做些什么来更好地支持这个用例吗?

2 个答案:

答案 0 :(得分:12)

使用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返回的未来可能会很有意思,如果你想回到一个处理内部未来结果的未来。如果有的话,它会保留当前未来的例外情况。但是,如果将来正常完成并且继续抛出,则抛出异常会异常完成。

不同之处在于future完成后发生的任何事情都会在下一次继续之前发生。如果您是exceptionally的最终用户,则使用thenAcceptfuture是等效的,但如果您要将未来提供给调用方,则任何一方都将在未完成的情况下处理通知(如果在后台,如果可能的话),很可能是exceptionally延续,因为您可能希望异常在进一步延续时级联。

答案 1 :(得分:2)

注意,exceptionally(Function<Throwable,? extends T> fn)也会返回CompletableFuture<T>。所以你可以进一步发展。

Function<Throwable,? extends T>的返回值意味着为下一个链式方法生成回退结果。因此,您可以从缓存中获取值,如果它不可用于DB。

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接受Supplier<T>而不是功能,那么它如何返回CompletableFuture<String>以进一步链接?

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

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

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