我在处理来自CompletableFuture方法的异常抛出时遇到问题。我认为应该可以从CompletableFuture的异常条款中抛出异常。例如,在下面的方法中,我预计executeWork
将抛出RuntimeException,因为我在各个exceptionally
子句中抛出一个,但是,这不起作用,我不知道为什么。< / p>
public void executeWork() {
service.getAllWork().thenAccept(workList -> {
for (String work: workList) {
service.getWorkDetails(work)
.thenAccept(a -> sendMessagetoQueue(work, a))
.exceptionally(t -> {
throw new RuntimeException("Error occurred looking up work details");
});
}
}).exceptionally(t -> {
throw new RuntimeException("Error occurred retrieving work list");
});
}
答案 0 :(得分:1)
您在这里做错了几件事(async programming is hard):
首先,正如@VGR指出的那样,当情况变坏时executeWork()
不会引发异常-因为所有实际工作都是在另一个线程上完成的。 executeWork()
实际上将立即返回-在安排了所有工作之后但没有完成任何工作。您可以在最后一个get()
上调用CompletableFuture
,这将等待工作完成或失败,并会引发任何相关异常。但这是强制同步,被认为是一种反模式。
第二,您不需要throw new RuntimeException()
句柄中的exceptionally()
-在您的情况下,实际上已调用了正确的错误(t
)。
查看类似的同步代码,您的示例如下所示:
try {
for (String work : service.getAllWork()) {
try {
var a = service.getWorkDetails(work);
sendMessageToQueue(work, a);
} catch (SomeException e) {
throw new RuntimeException("Error occurred looking up work details");
}
}
} catch (SomeOtherException e) {
throw new RuntimeException("Error occured retrieving work list");
}
因此,如您所见,捕获异常并抛出RuntimeException
(也隐藏了真正的错误)并没有让异常传播到可以处理它们的地方,这没有任何好处。
exceptionally()
步骤的目的是从异常中恢复-例如在从用户或IO检索数据失败或类似情况时放置默认值。示例:
service.getAllWork().thenApply(workList -> workList.stream()
.map(work -> service.getWorkDetails(work)
.thenAccept(a -> sendMessageToQueue(work, a)
.exceptionally(e -> {
reportWorkFailureToQueue(work, e);
return null;
})
)
).thenCompose(futStream ->
CompletableFuture.allOf(futStream.toArray(CompletableFuture[]::new)))
.exceptionlly(e -> {
// handle getAllWork() failures here, getWorkDetail/sendMessageToQueue
// failures were resolved by the previous exceptionally and converted to null values
});