传递给 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<Throwable>
,它需要Function<Throwable, ? extends T>
——在本例中为Function<Throwable, ? extends String>
。
这意味着我不能只记录错误,我必须想出一个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 库中做些什么,更好地支持这个用例吗?
【问题讨论】:
【参考方案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
的最终用户,则使用 exceptionally
和 thenAccept
是等效的,但是如果您向调用者提供未来返回,则任何一个都将在没有完成通知的情况下进行处理(就像在背景,如果可以的话),很可能是 exceptionally
延续,因为您可能希望异常在进一步的延续中级联。
【讨论】:
不幸的是,第一个实际上并没有编译,因为log.error(...)
是void
并且exceptionally()
想要返回未来的返回类型(在本例中为String
)。 (这从根本上导致了我的问题。)感谢 cmets 上流畅的 API 和链接。【参考方案2】:
注意,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);
如果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;
);
【讨论】:
以上是关于传递给 CompletableFuture.exceptionalally() 的异常处理程序是不是必须返回有意义的值?的主要内容,如果未能解决你的问题,请参考以下文章
如何将对象数组作为道具传递给组件,然后将数组的成员作为道具传递给嵌套组件?