带你读《2022技术人的百宝黑皮书》——响应式编程的复杂度和简化(1)https://developer.aliyun.com/article/1339639?groupCode=taobaotech
响应式编程的复杂度
响应式编程的复杂度来自于4个方面:
可以有0次、1次或多次数据产生,也就是数据流;
除了数据之外,还有能够标识错误和完成(正常结束); 数据流和数据流、数据流和过程的组合复杂度很高;
在上面的基础上,需要处理整个过程中线程切换、并发同步、数据缓冲等问题。
为了支持数据流的概念,可以产生0次、1次或多次数据产生,API设计需要把数据回调和结果回调分开,通常也会把错误回调和完成回调分开,这种接口被称为流式接口,一个标准的流式接口设计如下所示:
1typealias Func = () -> () 2typealias OnData<Data> = (Data) -> () 3typealias OnError = (Error) -> () 4typealias OnComplete = Func 5typealias StreamFunc<Data> = (@escaping OnData<Data>, @escaping OnError, @escaping OnCom- plete) -> ()
显然,流式接口是普通异步接口将一次结果向多次结果的推广,这种推广同时也增加了逻辑的复杂度。
我们可以通过一个逻辑上简单的例子来看一下流式接口的使用过程,为了关注于核心的复杂度,只会体现前3个方面,一方面是由于加入第4点的话会导致代码过于冗长混淆关注点,另一方面相信各位对第4点本身的复杂度和它引起的众多问题已经非常熟悉了。
这个例子很简单,只有三步:
- 假设需要为一个店铺提供一个订单展示页面,这些订单来自两个不同的平台“鹅鹅鹅”和“鸭鸭鸭”,他们各自提供了查询的接口(listOrders,为了简单假设他们提供的模型和接口完全一致);
- 订单列表需要展示用户的昵称等信息,需要通过对应平台的另外一个接口(queryUserInfo)查询;
- 由于SDK缓存、持久化、网络请求策略,数据无法一次性获取,这两个接口可能存在多次数据回调。
- 进一步简化问题,我们忽略变更处理、UI渲染和用户交互处理,仅仅考虑数据加载,这需要组合2个阶段的4次接口调用,先分别请求两个平台的订单,使用订单请求对应平台的userInfo,最后合并成完整数据:
// 数据怎么回调,什么情况结束,onError和onComplete分别在什么情况回调,保证有且仅有一次回调 func load(onData : OnData<[OrderObject]>?, onError : OnError?, onComplete : OnComplete?) { let orderServices = [OrderService("鹅鹅鹅"), OrderService("鸭鸭鸭")] // 记录整体请求的完成状态 var listOrderFinish = false var queryUserFinish = false // 记录各个请求的结果 var listOrderResults = orderServices.map{_ in false} var queryUserResults = [Bool]() for (index, orderService) in orderServices.enumerated() { orderService.listOrders { orders in // 已结束不处理 if (listOrderFinish) { return; } let index = queryUserResults.count queryUserResults[index] = false if let userService = getUserService(site: orderService.site){ let userIds = orders.map { order in order.userId } userService.queryUserInfo(userIds: userIds) { userInfoDict in if (listOrderFinish && queryUserFinish) { return; } let orderObjects = orders.map { order in OrderObject(order: order, userInfo: userInfoDict[order.userId]) } onData?(orderObjects) } onError: { error in // 如果是第一个错误,直接回调,同时标记为结束 if (!listOrderFinish || !queryUserFinish) { listOrderFinish = true queryUserFinish = true onError?(error) } } onComplete: { // 外层结束,内层也结束,才是最终结束 if (!listOrderFinish || !queryUserFinish) { queryUserResults[index] = true // 所有都结束,回调 if (listOrderFinish && !queryUserResults.contains(false)) { listOrderFinish = true onComplete?() } } } } else { let orderObjects = orders.map { order in OrderObject(order: order)
54 |
|
} |
55 |
|
onData?(orderObjects) |
56 |
|
|
57 |
|
queryUserResults[index] = true |
58 |
|
// 所有都结束,回调 |
59 |
|
if (listOrderFinish && !queryUserResults.contains(false)) { |
60 |
|
listOrderFinish = true |
61 |
|
onComplete?() |
62 |
|
} |
63 |
} |
|
64 |
} onError: { error in |
|
65 |
// 如果是第一个错误,直接回调,同时标记为结束 |
|
66 |
if (!listOrderFinish) { |
|
67 |
listOrderFinish = true |
|
68 |
onError?(error) |
|
69 |
} |
|
70 |
} onComplete: { |
|
71 |
// 注意,即使所有的请求都结束了,也不能回调结束,因为这里的结束只是代表Order请求结束,userInfo |
|
|
请求不一定结束 |
|
72 |
if (!listOrderFinish) { |
|
73 |
listOrderResults[index] = true |
|
74 |
// 所有都结束,回调 |
|
75 |
if (!listOrderResults.contains(false)) { |
|
76 |
listOrderFinish = true |
|
77 |
} |
|
78 |
} |
|
79 |
} |
|
80 |
|
|
81 |
} |
|
82 |
} |
带你读《2022技术人的百宝黑皮书》——响应式编程的复杂度和简化(3)https://developer.aliyun.com/article/1339637?groupCode=taobaotech