CompletableFuture 的异常处理容易写错,因为同一个异常在不同入口会换不同的包装。这篇逐项把包装规则、沉默的失败、几个回调的语义、allOf / anyOf 的失败时机,以及取消与超时的实际效果都量了一遍。
supplyAsync 里抛出:handle 拿到的异常 = CompletionException(原因 IllegalStateException:boom) join() 抛出:CompletionException(原因 IllegalStateException:boom) get() 抛出:ExecutionException(原因 IllegalStateException:boom) completeExceptionally(原始异常) 的那个 future:handle 拿到的异常 = IllegalStateException:boom 它的下游 thenApply 的 future:handle 拿到的异常 = CompletionException(原因 IllegalStateException:boom) completeExceptionally 的 future 上 join() 抛出:CompletionException(原因 IllegalStateException:boom) completeExceptionally 的 future 上 get() 抛出:ExecutionException(原因 IllegalStateException:boom) thenApply 里抛出:handle 拿到的异常 = CompletionException(原因 IllegalStateException:boom) exceptionally 在 supplyAsync 的 future 上收到的异常类型 = CompletionException(不是原始异常) exceptionally 在 completeExceptionally 的 future 上收到的异常类型 = IllegalStateException
runAsync 抛出异常、没有 join / handle:isCompletedExceptionally() = true,默认 UncaughtExceptionHandler 被调用 0 次,标准错误输出里没有任何堆栈(本进程到这里没有任何异常信息输出) thenRun 链上的后续阶段:isCompletedExceptionally() = true,没有被执行 对照:ExecutorService.execute 里抛出异常,默认 UncaughtExceptionHandler 被调用 1 次(累计)
whenComplete(只观察)在失败的 future 上:结果仍然失败 = true
whenComplete 的回调在成功的 future 上抛出 RuntimeException("B"):结果 = CompletionException(原因 RuntimeException:B)
whenComplete 的回调在失败的 future 上又抛出 B:结果的异常 = CompletionException(原因 IllegalStateException:A),被抑制的异常数 = 0(原异常保留,B 被丢弃或抑制)
handle 可以恢复:handle((v, e) -> e != null ? -1 : v) = -1;exceptionally(e -> -2) = -2
exceptionallyCompose(JDK 12)用另一个 future 恢复:77
handle 回调自己抛异常:CompletionException(原因 RuntimeException:C)300 ms 时:fast 已失败 = true,allOf 是否已完成 = false all.join() 在 1006 ms 时抛出:CompletionException(原因 IllegalStateException:100 ms 时失败) anyOf 的结果在 110 ms 时是:CompletionException(原因 IllegalStateException:先失败的)(第一个完成的是失败的,第二个成功的结果被忽略)
cancel(true) 返回 true,isCancelled() = true;底层任务照常跑完 = true,被中断 = false;下游 thenApply 的结果:CompletionException(原因 CancellationException:null)
orTimeout(200 ms):在 211 ms 时 join() 抛出 CompletionException(原因 TimeoutException:null)
700 ms 后底层任务仍然执行完了 = true
completeOnTimeout("fallback", 200 ms):fallback