![](/img/trans.png)
[英]Letting CompletableFuture exceptionally() handle a supplyAsync() Exception
[英]CompletableFuture#exceptionally rethrow checked exception
假設我想在遇到特定異常時用一些值進行恢復,否則返回失敗的 future 和異常。 我會期待這樣的事情:
public static void main(String[] args) {
CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if (throwable instanceof RuntimeException) {
return "All good";
}
throw throwable; // does not compile
});
}
public static String fetchValue() {
// code that potentially throws an exception
return "value";
}
如果fetchValue
function 會拋出一個已檢查的異常,我想在鏈式方法中處理它。 我試過return throwable
和throw throwable
,但都沒有編譯。 CompletableFuture
是否為這種情況提供任何解決方案? 我知道作為exceptionally
方法參數的Function
接口不會拋出任何異常 - 在這種情況下我只想返回已經失敗的未來。 我想找到使用 Java 8 的解決方案。
正如霍爾格所寫,這通常是不可能的。
但是,lombok 有一個技巧,那就是@SneakyThrows 。
public static void main(String[] args) {
CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if (throwable instanceof RuntimeException) {
return "All good";
}
FutureExample.reThrow(throwable);
// maybe a "return null" is necessary here (even when it is not reachable)
});
}
public static String fetchValue() {
// code that potentially throws an exception
return "value";
}
@SneakyThrows // <- threat checked exceptions in method-body as unchecked
public static void reThrow(Throwable throwable) {
throw throwable;
}
您還可以使用ExceptionUtils.rethrow()
(感謝vlp )將其存檔。
public static void main(String[] args) {
CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if (throwable instanceof RuntimeException) {
return "All good";
}
reThrow(throwable);
// maybe a "return null" is necessary here (even when it is not reachable)
});
}
在這種情況下,由於上一階段是基於Supplier
,因此無法接收已檢查的異常,該Supplier
不允許拋出已檢查的異常。
因此,您可以處理所有未檢查的異常,並為應該不可能發生的可拋出事件引發AssertionError
:
CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if (throwable instanceof RuntimeException) {
return "All good";
}
if(throwable instanceof Error) throw (Error)throwable;
throw new AssertionError(throwable);
});
否則,您可能會認為后續階段以及join()
調用者都將獲得除封裝在CompletionException
CompletionException
和CancellationException
外的所有異常。 例如,當我使用
public static void main(String[] args) {
CompletableFuture<String> f = CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if(throwable instanceof RuntimeException) {
throw (RuntimeException)throwable;
}
throw new Error();
});
f.whenComplete((s,t) -> {
if(t != null) {
System.err.println("in whenComplete handler ");
t.printStackTrace();
}
});
System.err.println("calling join()");
f.join();
}
public static String fetchValue() {
throw new IllegalStateException("a test is going on");
}
我懂了
in whenComplete handler
java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.lang.IllegalStateException: a test is going on
at FuturesExample.fetchValue(FuturesExample.java:23)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
... 6 more
calling join()
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.lang.IllegalStateException: a test is going on
at FuturesExample.fetchValue(FuturesExample.java:23)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
... 6 more
因此,我可以利用CompletionException
來包裝任意的throwable,這是因為CompletionException
不會再次被包裝。 所以如果我用
public static void main(String[] args) {
CompletableFuture<String> f = CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if(throwable instanceof CompletionException)
throwable = throwable.getCause();
System.err.println("wrapping '"+throwable+"' inside exceptionally");
throw new CompletionException(throwable);
});
f.whenComplete((s,t) -> {
if(t != null) {
System.err.println("in whenComplete handler ");
t.printStackTrace();
}
});
System.err.println("calling join()");
f.join();
}
public static String fetchValue() {
throw new IllegalStateException("a test is going on");
}
我懂了
wrapping 'java.lang.IllegalStateException: a test is going on' inside exceptionally
in whenComplete handler
java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
at FuturesExample.lambda$main$0(FuturesExample.java:12)
at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:986)
at java.base/java.util.concurrent.CompletableFuture$UniExceptionally.tryFire(CompletableFuture.java:970)
at java.base/java.util.concurrent.CompletableFuture.unipush(CompletableFuture.java:589)
at java.base/java.util.concurrent.CompletableFuture.uniExceptionallyStage(CompletableFuture.java:1002)
at java.base/java.util.concurrent.CompletableFuture.exceptionally(CompletableFuture.java:2307)
at FuturesExample.main(FuturesExample.java:8)
Caused by: java.lang.IllegalStateException: a test is going on
at FuturesExample.fetchValue(FuturesExample.java:24)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
calling join()
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
at FuturesExample.lambda$main$0(FuturesExample.java:12)
at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:986)
at java.base/java.util.concurrent.CompletableFuture$UniExceptionally.tryFire(CompletableFuture.java:970)
at java.base/java.util.concurrent.CompletableFuture.unipush(CompletableFuture.java:589)
at java.base/java.util.concurrent.CompletableFuture.uniExceptionallyStage(CompletableFuture.java:1002)
at java.base/java.util.concurrent.CompletableFuture.exceptionally(CompletableFuture.java:2307)
at FuturesExample.main(FuturesExample.java:8)
Caused by: java.lang.IllegalStateException: a test is going on
at FuturesExample.fetchValue(FuturesExample.java:24)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
這在堆棧跟蹤中略有不同,但是對接收/捕獲異常的代碼沒有影響,因為在兩種情況下,它都是包裹IllegalStateException
的CompletionException
。
因此,回到問題的示例,您可以使用
CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if (throwable instanceof RuntimeException) { // includes CompletionException
return "All good";
}
throw new CompletionException(throwable);
});
由於CompletionException
是RuntimeException
,因此此代碼對其進行處理,並避免將CompletionException
包裝在另一個CompletionException
。 否則,模式將是
.exceptionally(throwable -> {
if (some condition) {
return some value;
}
throw throwable instanceof CompletionException?
(CompletionException)throwable: new CompletionException(throwable);
});
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.