回调地狱有什么关系?矫情!本菜狗只是觉得不该这样写。

429 阅读5分钟

作为工作有些年头的菜狗程序员,我不太爱写文章。
第一,总觉得有很多简单的编程知识或技巧无须重复阐述,大佬肯定都比我熟。 第二,就是太懒了。 第三,千人千面,文人相轻,大佬们争来争去,反而失去了技术交流的原有精神。

本文仅献给遇到同样问题的小伙伴们,仅供参考。

最近一段时间闲了下来,在review代码的时候,我突然觉得原来有些知识,很多小伙伴其实并不清楚。比如我最近发现的一个问题,新来的同学喜欢三个四个接口回调嵌套在一起,后期很难维护和阅读吗,俗称回调地狱。那该如何解决了?以下的几种方式我都使用过,现在罗列如下,供集帅们参考。废话有点多,直接开始吧

Kotlin Flow

话说协程可是个好东西,第一次接触后,就爱上了它。但这个有个最大局限性,这个基本也就Android开发的小伙伴在用。(当然,也有人用它代替Java写后端代码)

// 实体类
data class UserInfo(val id: Int, val name: String)
data class Order(val id: Int, val name: String)
data class OrderDetail(val orderId: Int, val detail: String)

// 模拟网络请求
fun fetchUserInfo(userId: Int): Flow<UserInfo> = flow {
    // 模拟网络延迟
    delay(500)
    // 模拟已获取用户信息
    emit(UserInfo(userId, "Lele"))
}

fun fetchOrderList(userInfo: UserInfo): Flow<List<Order>> = flow {
    // 模拟网络延迟
    delay(500)
    // 模拟已获取订单列表
    emit(listOf(Order(1, "Order 1"), Order(2, "Order 2")))
}

fun fetchOrderDetails(orderId: Int): Flow<OrderDetail> = flow {
    // 模拟网络延迟
    delay(500)
    // 模拟获取订单详情
    emit(OrderDetail(orderId, "Order Detail $orderId"))
}

fun main() = runBlocking {
    // Flow 链式调用
    flowOf(123) // 模拟 userId
        .flowOn(Dispatchers.IO) // 在 IO 线程执行
        .flatMapConcat { userId -> fetchUserInfo(userId) } // 获取用户信息
        .flatMapConcat { userInfo -> fetchOrderList(userInfo) } // 获取订单列表
        .flatMapConcat { orderList -> orderList.asFlow() } // 将订单列表转为单个订单流
        .flatMapConcat { order -> fetchOrderDetails(order.id) } // 获取每个订单详情
        .flowOn(Dispatchers.Default) // 切换到 Default 线程处理结果
        .collect { orderDetail ->
            println("Order details: $orderDetail")
        }
}

其实,写到这儿,我想了想,这个场景有点扯。Kotlin的Suspend函数能更加直观的解决该问题,我推荐使用Suspend函数。但为了跟Rxjava进行对比,我还是写了。只是做个参考罢了。各位集帅大佬肯定比我理解的更深刻。

让人又爱又恨的Rxjava

Rxjava那是相当的强大,响应式编程是不少很多人的心头好。但它对于新手来说过于复杂,操作符多的令人发指。这里不铺开讲Rxjava的用法,只讲如何处理回调地狱的问题。

举个简单例子: 获取用户信息、根据用户信息获取订单列表、根据订单列表获取每个订单的详细信息。请看小伙伴的实现:

fetchUserInfo(userId, userInfo -> { 
    fetchOrderList(userInfo, orderList -> { 
        for (Order order : orderList) { 
            fetchOrderDetails(order.getId(), orderDetails -> { 
            System.out.println("Order details: " + orderDetails); 
           }); 
       }
    }); 
 });

这种嵌套的回调方式不仅难以阅读,而且容易出错,尤其是当有更多的异步操作需要串联时。 那这个时候RXjava就可以派上用场了。

下面看解决方案: 首先,我们需要定义几个模拟的异步方法:

    public static Observable<UserInfo> fetchUserInfo(int userId) {
        return Observable.create(emitter -> {
            // 模拟网络延迟
            try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
            //模拟已获取User 信息
            UserInfo userInfo = new UserInfo(userId, "Lele");
            emitter.onNext(userInfo);
            emitter.onComplete();
        });
    }

    public static Observable<List<Order>> fetchOrderList(UserInfo userInfo) {
        return Observable.create(emitter -> {
            // 模拟网络延迟
            try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
            //模拟已获取订单列表
            List<Order> orders = Arrays.asList(new Order(1, "Order 1"), new Order(2, "Order 2"));
            emitter.onNext(orders);
            emitter.onComplete();
        });
    }

    public static Observable<OrderDetail> fetchOrderDetails(int orderId) {
        return Observable.create(emitter -> {
            // 模拟网络延迟
            try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
            //模拟获取订单详情
            OrderDetail detail = new OrderDetail(orderId, "Order Detail " + orderId);
            emitter.onNext(detail);
            emitter.onComplete();
        });
    }

然后,我们可以使用 RxJava 来链式地调用这些方法:

        int userId = 123;
        // 使用 RxJava 链式调用
        Observable.just(userId)
            .subscribeOn(Schedulers.io()) // 在 IO 线程执行
            .flatMap(fetchUserInfo) // 获取用户信息
            .flatMap(userInfo -> fetchOrderList(userInfo)) // 获取订单列表
            .flatMapObservable(orderList -> Observable.fromIterable(orderList)) // 将订单列表转换为单个订单流
            .flatMap(order -> fetchOrderDetails(order.getId())) // 获取每个订单的详情
            .observeOn(Schedulers.newThread()) // 切换到新的线程来处理结果
            .subscribe(orderDetail -> System.out.println("Order details: " + orderDetail));

原生儿子CompletableFuture,推荐,虽然我喜欢用Rxjava

CompletableFuture 是 Java 8引入的一个类,它扩展了 Future 接口,并提供了丰富的功能来处理异步编程。它不需要引入任何的第三方包,可以直接使用,是java的原生类。

// 模拟获取用户信息的方法
    public static CompletableFuture<UserInfo> fetchUserInfo(String userId) {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟耗时操作
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return new UserInfo(userId, "John Doe");
        });
    }

    // 模拟获取订单列表的方法
    public static CompletableFuture<List<Order>> fetchOrderList(UserInfo userInfo) {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟耗时操作
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return List.of(
                new Order("order1", userInfo.getUserId()),
                new Order("order2", userInfo.getUserId())
            );
        });
    }

    // 模拟获取订单详细信息的方法
    public static CompletableFuture<String> fetchOrderDetails(String orderId) {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟耗时操作
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Details for order: " + orderId;
        });
    }
// 使用 CompletableFuture 链式调用来实现嵌套回调逻辑
        CompletableFuture<Void> future = fetchUserInfo(userId)
            .thenCompose(userInfo -> fetchOrderList(userInfo)
                .thenCompose(orderList -> {
                    // 并行获取每个订单的详细信息
                    List<CompletableFuture<Void>> futures = orderList.stream()
                        .map(order -> fetchOrderDetails(order.getId())
                            .thenAccept(orderDetails -> System.out.println("Order details: " + orderDetails))
                        )
                        .collect(Collectors.toList());

                    // 等待所有订单详细信息获取完成
                    return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
                })
            );

        // 等待所有异步操作完成
        future.get();

写在最后

我这边只列举这三种,更多的方案还有Reactor等等,这边不再展开。要是小伙伴觉得记不住,就记住了一点,回调嵌套不优雅,直接在ChatGpt、通义千问搜索”如何解决回调嵌套问题“,照抄就可以了。AI写的比我好多了。这篇文章的最大目的,只是告诉大家,这个回调嵌套写法不该是这样的。

那么问题来了,我们到底用哪种了?我的建议是看自己喜欢。如果项目没有集成RxJava,那么用CompletableFuture吧。Android的小伙伴要是使用Kotlin编码,那就使用Supend函数或者Flow。其他的就随便啦。管他黑猫白猫,能抓耗子就行。技术不需要太纠结选型,好用合适就行,整天花里胡哨干啥?