传递给 CompletableFuture.exceptionally() 的异常处理程序是否必须 return 一个有意义的值?
Does an exception handler passed to CompletableFuture.exceptionally() have to return a meaningful value?
我习惯了 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>
.
这意味着我不能只记录错误,我必须在错误情况下为return想出一个String
值,并且没有任何意义String
错误情况下的值为 return。我可以 return 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 个库中做些什么,更好地支持这个用例?
请注意,exceptionally(Function<Throwable,? extends T> fn)
也是 returns CompletableFuture<T>
。所以你可以进一步链接。
Function<Throwable,? extends T>
的 return 值旨在为下一个链接方法生成回退结果。因此,如果数据库不可用,您可以从缓存中获取值。
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>
而不是函数,那么它如何 return 一个 CompletableFuture<String>
来进一步链接?
我想你想要 exceptionally
的变体,它会 return void
。但不幸的是,不,没有这样的变体。
因此,在您的情况下,您可以安全地 return 来自此后备函数的任何值,如果您不 return 此 future
对象并且不在您的代码中进一步使用它(因此它不能被进一步链接)。最好不要将它分配给变量。
CompletableFuture<String>.supplyAsync(/*get from DB*/)
.thenAccept(this::handleResult)
.exceptionally((t) -> {
log.error("Unexpected error", t);
return null;
});
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);
}
});
这里有趣的部分是您在示例中链接了 futures。看似流畅的语法实际上是链式的futures,不过这里好像你不想要那样。
如果您想 return 一个处理具有内部未来结果的事物的未来,return 由 whenComplete
编辑的未来可能会很有趣。它保留当前未来的异常,如果有的话。但是,如果 future 正常完成并且继续抛出异常,它将异常完成并抛出异常。
不同之处在于,future
完成后发生的任何事情都将在下一次继续之前发生。如果您是 future
的最终用户,则使用 exceptionally
和 thenAccept
是等价的,但是如果您向调用者提供未来返回,则任何一个都将在没有完成的情况下进行处理通知(如果可能的话,就像在后台一样),很可能是 exceptionally
延续,因为您可能希望异常在进一步的延续上级联。
我习惯了 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>
.
这意味着我不能只记录错误,我必须在错误情况下为return想出一个String
值,并且没有任何意义String
错误情况下的值为 return。我可以 return 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 个库中做些什么,更好地支持这个用例?
请注意,exceptionally(Function<Throwable,? extends T> fn)
也是 returns CompletableFuture<T>
。所以你可以进一步链接。
Function<Throwable,? extends T>
的 return 值旨在为下一个链接方法生成回退结果。因此,如果数据库不可用,您可以从缓存中获取值。
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>
而不是函数,那么它如何 return 一个 CompletableFuture<String>
来进一步链接?
我想你想要 exceptionally
的变体,它会 return void
。但不幸的是,不,没有这样的变体。
因此,在您的情况下,您可以安全地 return 来自此后备函数的任何值,如果您不 return 此 future
对象并且不在您的代码中进一步使用它(因此它不能被进一步链接)。最好不要将它分配给变量。
CompletableFuture<String>.supplyAsync(/*get from DB*/)
.thenAccept(this::handleResult)
.exceptionally((t) -> {
log.error("Unexpected error", t);
return null;
});
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);
}
});
这里有趣的部分是您在示例中链接了 futures。看似流畅的语法实际上是链式的futures,不过这里好像你不想要那样。
如果您想 return 一个处理具有内部未来结果的事物的未来,return 由 whenComplete
编辑的未来可能会很有趣。它保留当前未来的异常,如果有的话。但是,如果 future 正常完成并且继续抛出异常,它将异常完成并抛出异常。
不同之处在于,future
完成后发生的任何事情都将在下一次继续之前发生。如果您是 future
的最终用户,则使用 exceptionally
和 thenAccept
是等价的,但是如果您向调用者提供未来返回,则任何一个都将在没有完成的情况下进行处理通知(如果可能的话,就像在后台一样),很可能是 exceptionally
延续,因为您可能希望异常在进一步的延续上级联。