使用CompletableFuture适应异常的正确方法 [英] Proper way to adapt an exception with CompletableFuture
问题描述
我正在链接CompletableFuture来适应例外。虽然我有一些工作,我不明白为什么它的工作。
I am working on chaining CompletableFuture to adapt an exception. While I have something that is working, I don't understand why it works.
@Test
public void futureExceptionAdapt() throws ExecutionException, InterruptedException {
class SillyException extends Exception { }
class AdaptedException extends Exception { AdaptedException(SillyException silly) { } }
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
sleepForThreeSeconds();
if (true)
throw new CompletionException(new SillyException());
return 5;
})
.thenApplyAsync(val -> val * 10);
CompletableFuture<Integer> futureAdaptException = future.exceptionally((t) -> {
if (t instanceof CompletionException && t.getCause() instanceof SillyException) {
System.out.println("adapt SillyException to AdaptedException");
SillyException silly = (SillyException) t.getCause();
future.obtrudeException(new AdaptedException(silly));
}
return null;
});
try {
future.get();
fail("future should have failed with an exception");
} catch (ExecutionException e) {
assertTrue("got exception: " + getCauseClass(e),
e.getCause() instanceof AdaptedException);
}
// I am not sure why the above succeeds
// because I did not call futureAdaptException.get()
// According to the IDE futureAdaptException is an unused variable at this point
assertTrue("expect futureAdaptException to have failed with AdaptedException but instead the result is: " + futureAdaptException.get(),
futureAdaptException.isCompletedExceptionally());
}
private static void sleepForThreeSeconds() {
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
}
}
private static String getCauseClass(Throwable t) {
if (t.getCause() == null) return "null";
return t.getCause().getClass().getName();
}
第一个问题是为什么 futureAdaptException
调用 future.get()
?
First question is why is futureAdaptException
called when I simply call future.get()
?
其次,是否有任何方式获取 futureAdaptException.get()
将失败并出现所需的异常?因为我不想创建一个虚拟对象,如果我不必。您不能从lambda修改 futureAdaptException
。
Secondly, is there any way to get futureAdaptException.get()
to fail with the desired exception? Because I don't want to create a dummy object if I don't have to. You cannot modify futureAdaptException
from the lambda.
或者也许有更好的方式来适应异常。或者也许有一个变种的异常()
,保持对象处于异常阶段(似乎返回null
原因 futureAdaptException
将对象放入正常阶段,值为 null
)。或者也许我们不应该在 CompletableFuture
中调整例外。
Or maybe there is a better way to adapt exceptions. Or maybe there is a variant of exceptionally()
that keeps the object in an exception stage (seems the return null
causes futureAdaptException
to put the object into normal stage with value null
). Or maybe we shouldn't be adapting exceptions in CompletableFuture
s.
@Test
public void futureStrangeBehaviorFromChaining1() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("sleep for 3 seconds then return 5");
sleepForThreeSeconds();
return 5;
});
future.thenApplyAsync(val -> {
System.out.println("multiply by 3");
return val*3;
});
sleepForFiveSeconds();
assertEquals(5, future.get().intValue());
sleepForFiveSeconds();
assertEquals(5, future.get().intValue());
}
@Test
public void futureStrangeBehaviorFromChaining2() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("sleep for 3 seconds then return 5");
sleepForThreeSeconds();
return 5;
})
.thenApplyAsync(val -> {
System.out.println("multiply by 3");
return val*3;
});
sleepForFiveSeconds();
assertEquals(15, future.get().intValue());
sleepForFiveSeconds();
assertEquals(15, future.get().intValue());
}
推荐答案
当您调用
不是调用。会发生什么事情是用 future.get()
时,futureAdaptException future.exceptionally()
创建的,所以当将来
完成异常时,它会自动触发。
futureAdaptException
is not "called" when you call future.get()
. What happens is that you created it with future.exceptionally()
so it will automatically be triggered when future
completes exceptionally.
所以即使 futureAdaptException
未使用(所以你可以删除变量), ()
仍然有一个副作用。
So even though futureAdaptException
is unused (so you could remove the variable), exceptionally()
still has a side effect.
您获得的 CompletableFuture
code> except()将成功或失败,具体取决于您在传入功能中的功能。如果你想要失败,你仍然可以再次抛出一个异常:
The CompletableFuture
that you get from exceptionally()
will succeed or fail depending on what you do in the passed-in function. If you want it to fail, you can still throw an exception again:
CompletableFuture<Integer> futureAdaptException = future.exceptionally((t) -> {
if (t instanceof CompletionException && t.getCause() instanceof SillyException) {
System.out.println("adapt SillyException to AdaptedException");
SillyException silly = (SillyException) t.getCause();
final AdaptedException ex = new AdaptedException(silly);
future.obtrudeException(ex);
throw new CompletionException(ex);
}
return null;
});
请注意,您应该避免使用 obtrudeException()
因为这是非确定性的。如果事实上,我感到惊讶的是,你的第一个断言是成功的。由 except()
返回的 CompletableFuture
将以与原始结果相同的结果完成,如果成功,则应该工作与此相反。
Note that you should probably avoid using obtrudeException()
as this is non-deterministic. If fact, I am surprised that your first assert succeeds¹. The CompletableFuture
returned by exceptionally()
would complete with the same result as the original if it succeeds, so you should work with that one instead.
¹我绝对认为这是由于JDK中的一个错误。如果您在异常()
中添加了一个 sleepForThreeSeconds()
,则测试仍然通过。但是,如果在 future.get()
之前添加了3秒以上的睡眠,则断言失败,您将获得原始异常。如果在完成之前调用 get()
,它似乎等待异常()
也执行。我已经发布了这个问题了解更好。
¹ I definitely think this is due to a bug in the JDK. If you add a sleepForThreeSeconds()
in exceptionally()
, the test still passes. However, if you add a sleep for more than 3s before future.get()
the assert fails and you get the original exception. If you call get()
before completion, it appears to wait for the exceptionally()
to execute as well. I have posted this question to understand this better.
这篇关于使用CompletableFuture适应异常的正确方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!