作为工作有些年头的菜狗程序员,我不太爱写文章。
第一,总觉得有很多简单的编程知识或技巧无须重复阐述,大佬肯定都比我熟。
第二,就是太懒了。
第三,千人千面,文人相轻,大佬们争来争去,反而失去了技术交流的原有精神。
本文仅献给遇到同样问题的小伙伴们,仅供参考。
最近一段时间闲了下来,在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写的比我好多了。这篇文章的最大目的,只是告诉大家,这个回调嵌套写法不该是这样的。