RxSwift使用Observable.merge合并双API请求时首结果被清空的排查
问题根因
核心原因是对Observable.merge的行为存在认知错位,它的作用是按发射时间转发多个上游序列的所有事件,而非等待所有上游完成后把结果合并成一个整体,这是最终结果被覆盖的根源。
具体的事件流实际流转过程如下:
- 代码中给
ctgs、itms两个请求序列都加了startWith([])和错误兜底逻辑,两个序列被订阅时会立刻发射一个空数组,后续各自在接口返回时再发射对应的结果数组 merge作为转发管道,会把两个序列发射的每一个数组值,依次直接传给下游的rootItems绑定动作- 由于两个接口并行发起,谁的返回值先被
merge转发,rootItems就会先更新为谁的数组;后返回的接口结果被merge再次转发时,会直接覆盖之前rootItems存储的旧值,自然不会出现两个数组合并的效果
这套逻辑本质上是用事件转发的管道去实现“多请求结果聚合”的目标,方向本身就存在偏差。
符合预期的实现方式
如果要实现“并行发起两个接口请求,等两个请求都返回后,把分类数组和搜索结果数组拼接成一个完整列表再更新数据源”的目标,需要用适合做多请求结果聚合的操作符替代嵌套merge:
let refreshText = refreshTrigger.withLatestFrom(queryText) Observable.merge(refreshText, queryText) .flatMapLatest { [weak self] query -> Observable<[Codable]> in guard let strongSelf = self else { return .empty() } // 分类请求:异常时兜底为空数组 let categories = (try? strongSelf.getCategories()) .flatMap { $0.catchErrorJustReturn([]) } ?? .just([]) // 搜索请求:重试3次后异常兜底为空数组 let searchResults = strongSelf.getSearchResults(query) .retry(3) .catchErrorJustReturn([]) // 等两个请求都拿到有效结果后,拼接两个数组统一发射 return Observable.zip(categories, searchResults) .map { $0 + $1 } .catchErrorJustReturn([]) } .bind(to: rootItems) .disposed(by: disposeBag)
调整说明
- 移除了两个请求序列上多余的
startWith([]):这类操作会在请求尚未完成时就提前向下游发射空数组,造成不必要的列表闪烁和数据源临时覆盖 - 用
Observable.zip替代嵌套的merge:zip会等待两个上游序列都各自发射首个有效元素后,将两个值打包为元组统一触发一次下游发射,刚好匹配“两个请求并行、等全部返回后再聚合更新”的需求 - 统一将错误兜底逻辑放在聚合完成的外层,避免中间值无序发射造成数据源被错误覆盖
- 适配原方法的
throws特性,把序列生成阶段的抛出异常做了兜底,避免因为方法本身抛错导致整个流中断
如果业务场景需要分阶段更新列表(比如分类接口先返回就先展示分类内容,搜索结果接口返回后再把结果追加到列表末尾),则不能直接将聚合后的数组流绑定到替换式的
BehaviorRelay<[Codable]>,需要额外维护列表状态,将每次收到的分片数据累加到现有数组中,否则依然会出现覆盖问题。
内容的提问来源于stack exchange,提问作者Makalele
相关产品推荐
相关产品推荐

