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

Posted

技术标签:

【中文标题】传递给 CompletableFuture.exceptionalally() 的异常处理程序是不是必须返回有意义的值?【英文标题】:Does an exception handler passed to CompletableFuture.exceptionally() have to return a meaningful value?传递给 CompletableFuture.exceptionalally() 的异常处理程序是否必须返回有意义的值? 【发布时间】:2016-10-12 17:42:37 【问题描述】:

我已经习惯了 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&lt;Throwable&gt;,它需要Function&lt;Throwable, ? extends T&gt;——在本例中为Function&lt;Throwable, ? extends String&gt;

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

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

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

如果exceptionally() 得到Function&lt;Throwable, Supplier&lt;T&gt;&gt;,我至少可以做这样的事情--

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

-- 但它没有。

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

【问题讨论】:

【参考方案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);
        
    );

这里有趣的部分是您在示例中链接了期货。看似流畅的语法其实是链式的future,但这里好像不想这样。

whenComplete 返回的未来可能会很有趣,如果你想返回一个处理具有内部未来结果的东西的未来。它保留当前未来的异常,如果有的话。但是,如果 future 正常完成并且 continuation 抛出,它将异常完成并抛出异常。

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

【讨论】:

不幸的是,第一个实际上并没有编译,因为log.error(...)void 并且exceptionally() 想要返回未来的返回类型(在本例中为String)。 (这从根本上导致了我的问题。)感谢 cmets 上流畅的 API 和链接。【参考方案2】:

注意,exceptionally(Function&lt;Throwable,? extends T&gt; fn) 也返回 CompletableFuture&lt;T&gt;。这样你就可以更进一步了。

Function&lt;Throwable,? extends T&gt; 的返回值旨在为下一个链式方法生成回退结果。因此,例如,如果数据库中不可用,则可以从缓存中获取值。

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&lt;T&gt; 而不是函数,那么它如何返回CompletableFuture&lt;String&gt; 以进一步链接?

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

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

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

【讨论】:

以上是关于传递给 CompletableFuture.exceptionalally() 的异常处理程序是不是必须返回有意义的值?的主要内容,如果未能解决你的问题,请参考以下文章

无法正确地将值从父母传递给孩子和孩子传递给父母

如何将对象数组作为道具传递给组件,然后将数组的成员作为道具传递给嵌套组件?

为啥将 ClientID 传递给 javascript 函数会传递整个控件?

将变量传递给 jQuery 插件参数

如何将环境变量传递给传递给xmllint的命令?

将 JSON 数据从 php 传递给 html-data 属性,然后传递给 Javascript