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

如何链式调用CompletableFuture并在首次成功时终止?

串行尝试直至成功:CompletableFuture链式实现方案

你提到的场景非常典型——基于串行物理线路的设备查询,必须逐一尝试直到成功,同时要避免CompletableFuture.anyOf那种“同时启动所有任务”的资源浪费。其实用CompletableFuture的链式API就能完美解决,核心思路是仅在前一次尝试明确失败后,才启动下一次尝试,完全串行执行,成功就立即终止。

核心实现思路

我们可以利用exceptionallyCompose(JDK 9+支持)或者结合thenCompose+异常处理,把各个尝试任务串联成一条“失败则继续”的链:

  • 先执行第一个尝试任务
  • 如果成功,直接返回结果,后续任务不会被触发
  • 如果失败,自动启动下一个尝试任务
  • 以此类推,直到某个任务成功,或者所有尝试都失败

代码示例

场景1:查询失败时抛出异常(推荐)

如果你的设备查询API在失败时会抛出异常(比如Modbus通信超时、响应错误),用exceptionallyCompose是最简洁的方式:

import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import java.util.concurrent.CompletableFuture;

public class DeviceQueryAttempts {

    // 模拟你的设备查询API,失败时抛出异常
    private static CompletableFuture<String> queryDevice(String deviceType) {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟尝试1失败,尝试2成功
            if ("type1".equals(deviceType)) {
                throw new RuntimeException("Query type1 failed: timeout");
            }
            return String.format("Successfully queried device type: %s", deviceType);
        });
    }

    // 递归串联所有尝试
    private static CompletableFuture<String> trySequentialAttempts(List<Supplier<CompletableFuture<String>>> attempts) {
        if (attempts.isEmpty()) {
            // 所有尝试都失败,返回失败的Future
            return CompletableFuture.failedFuture(new IllegalStateException("All device query attempts failed"));
        }
        // 取出第一个尝试
        Supplier<CompletableFuture<String>> firstAttempt = attempts.get(0);
        List<Supplier<CompletableFuture<String>>> remainingAttempts = attempts.subList(1, attempts.size());
        
        // 执行第一个尝试,失败则递归执行剩余尝试
        return firstAttempt.get()
                .exceptionallyCompose(throwable -> trySequentialAttempts(remainingAttempts));
    }

    public static void main(String[] args) {
        // 定义所有尝试顺序
        List<Supplier<CompletableFuture<String>>> queryAttempts = Arrays.asList(
                () -> queryDevice("type1"),
                () -> queryDevice("type2"),
                () -> queryDevice("type3")
        );

        // 执行串行尝试
        trySequentialAttempts(queryAttempts)
                .thenAccept(result -> System.out.println("Final result: " + result))
                .exceptionally(throwable -> {
                    System.err.println("All attempts failed: " + throwable.getMessage());
                    return null;
                });
    }
}

场景2:查询失败返回特殊标记值

如果你的API用返回值(比如null、特定字符串)表示失败,那可以用thenCompose来判断结果并决定是否继续:

private static CompletableFuture<String> trySequentialAttemptsWithResultCheck(List<Supplier<CompletableFuture<String>>> attempts) {
    if (attempts.isEmpty()) {
        return CompletableFuture.failedFuture(new IllegalStateException("All attempts failed"));
    }
    Supplier<CompletableFuture<String>> firstAttempt = attempts.get(0);
    List<Supplier<CompletableFuture<String>>> remaining = attempts.subList(1, attempts.size());
    
    return firstAttempt.get()
            .thenCompose(result -> {
                if (isQuerySuccess(result)) { // 自定义成功判断逻辑
                    return CompletableFuture.completedFuture(result);
                } else {
                    return trySequentialAttemptsWithResultCheck(remaining);
                }
            });
}

// 自定义成功判断方法
private static boolean isQuerySuccess(String result) {
    return result != null && !result.startsWith("FAILED");
}

为什么比anyOf更好?

  • 完全串行执行:严格按照顺序执行,符合物理线路的串行通信要求,不会出现并行请求冲突
  • 无资源浪费:只有前一次尝试失败后,才会启动下一次,成功后立即终止,不会让后续任务白跑
  • 逻辑清晰:链式结构直观体现“失败则重试”的业务逻辑,比anyOf+取消任务的方式更简洁

注意事项

  • 如果使用JDK 8,没有exceptionallyCompose,可以用exceptionally+thenCompose替代:
    return firstAttempt.get()
            .exceptionally(throwable -> {
                // 这里需要返回一个占位值,然后在thenCompose里处理
                return null;
            })
            .thenCompose(result -> {
                if (result != null) {
                    return CompletableFuture.completedFuture(result);
                } else {
                    return trySequentialAttempts(remainingAttempts);
                }
            });
    
  • 记得处理所有尝试失败的情况,避免Future一直处于未完成状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:17:03