You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何检测CompletableFuture调用链中的异常来源并根据来源执行不同逻辑

解析CompletableFuture异常来源与区分逻辑

首先直接回答你的疑问:whenComplete里的CompletionException包裹的异常取决于代码的执行分支:

  • 如果cf.completeExceptionally()被触发(随机数<0.5):此时exceptionally会执行并重新抛出初始的RuntimeException("oops"),而thenAcceptAsync根本不会运行(因为上游Future已经处于异常状态,thenAcceptAsync只响应正常完成的回调)。
  • 如果cf正常完成(随机数≥0.5):exceptionally不会触发,thenAcceptAsync执行并抛出自己的RuntimeException("oops"),这个异常会被包装成CompletionException传递过来。

接下来是核心问题:怎么区分这两个异常的来源?因为现在它们的消息都是"oops",直接判断消息不靠谱,推荐两种实用方案:

方案一:自定义异常类(最可靠)

给不同来源的异常定义专属的子类,这样可以通过类型直接区分,完全避免消息重复的问题:

// 定义两个自定义异常,分别标记不同来源
class InitialStageException extends RuntimeException {
    public InitialStageException(String message) {
        super(message);
    }
}

class CallbackStageException extends RuntimeException {
    public CallbackStageException(String message) {
        super(message);
    }
}

// 修改你的业务代码
public static void main(String[] args) {
    CompletableFuture<Void> cf = new CompletableFuture<>();
    cf
        .exceptionally(ex -> {
            // 重新抛出初始阶段的自定义异常
            throw (InitialStageException) ex;
        })
        .thenAcceptAsync((unused) -> {
            // 抛出回调阶段的自定义异常
            throw new CallbackStageException("callback oops");
        })
        .whenComplete((unused, ex) -> {
            if (ex != null) {
                // 拆解CompletionException拿到原始异常
                Throwable originalEx = ex.getCause();
                if (originalEx instanceof InitialStageException) {
                    // 执行初始异常的处理逻辑
                    System.out.println("处理初始阶段异常:" + originalEx.getMessage());
                } else if (originalEx instanceof CallbackStageException) {
                    // 执行回调阶段的处理逻辑
                    System.out.println("处理回调阶段异常:" + originalEx.getMessage());
                }
            }
        });
    if(Math.random() < 0.5) {
        // 抛出初始阶段的自定义异常
        cf.completeExceptionally(new InitialStageException("initial oops"));
    } else {
        cf.complete(null);
    }
}

方案二:给异常设置唯一标识(快速临时方案)

如果不方便自定义异常类,可以给不同来源的异常设置唯一的消息或添加自定义属性,这里以唯一消息为例:

public static void main(String[] args) {
    CompletableFuture<Void> cf = new CompletableFuture<>();
    cf
        .exceptionally(ex -> {
            throw (RuntimeException) ex;
        })
        .thenAcceptAsync((unused) -> {
            throw new RuntimeException("SOURCE_CALLBACK: oops");
        })
        .whenComplete((unused, ex) -> {
            if (ex != null) {
                Throwable originalEx = ex.getCause();
                String msg = originalEx.getMessage();
                if (msg.startsWith("SOURCE_INITIAL:")) {
                    System.out.println("处理初始异常逻辑");
                } else if (msg.startsWith("SOURCE_CALLBACK:")) {
                    System.out.println("处理回调异常逻辑");
                }
            }
        });
    if(Math.random() < 0.5) {
        cf.completeExceptionally(new RuntimeException("SOURCE_INITIAL: oops"));
    } else {
        cf.complete(null);
    }
}

关键注意点

  • CompletableFuture的回调中抛出的异常都会被自动包装成CompletionException,所以必须通过getCause()获取原始异常。
  • exceptionally是异常恢复方法:只有上游Future异常时才会执行;如果它本身抛出异常,后续依赖上游正常完成的回调(比如thenAcceptAsync)不会执行,这正好符合你"中断后续逻辑"的需求。

内容的提问来源于stack exchange,提问作者PentaKon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 22:27:45