如何使用Combine对数组中每个对象发起多个API请求?
问题描述
我调用一个函数得到了类型为AnyPublisher<[Object], Error>的结果,现在需要遍历数组里的每个对象,用单个对象的属性发起至少3个API请求(异步同步均可),再结合原对象与各API返回的对象创建新对象并发布。
流程如下:
AnyPublisher<[Object], Error> | 遍历对象数组 | 利用对象的某个属性发起3个API请求 | 结合原对象与API返回对象,创建并发布新对象
请问该怎么实现?
另外我写了一段测试代码,是基于另一篇帖子的代码修改的,但它在内部flatMap完成后就执行map,而不是收集所有发布者后再执行map。请问我哪里错了?
代码如下:
struct FoodDetailInput { let appear: AnyPublisher<Void, Never> } let appearRecord = PassthroughSubject<Void, Never>()// 进入详情页时触发 let input = FoodDetailInput(appear: appearRecord.eraseToAnyPublisher()) let foodDetails = input.appear .flatMap({[unowned self] _ in self.dataProvider.ingredients(for: food.id) }) .flatMap({ ingredients -> AnyPublisher<Ingredient, Error> in Publishers.Sequence(sequence: ingredients).setFailureType(to: Error.self).eraseToAnyPublisher() }) .flatMap({[unowned self] singleIngredient in self.dataProvider.variants(for: food.id, ingredientName: singleIngredient.id) .flatMap { variants -> AnyPublisher<DetailPageObject, Error> in var singleDetailObject = DetailPageObject(ingredient: singleIngredient) singleDetailObject.variants = variants return Just(singleDetailObject).setFailureType(to: Error.self).eraseToAnyPublisher() } .collect() .map { detailPageObjects -> PageDetailsState in PageDetailsState.success(detailPageObjects) } }) .replaceError(with:PageDetailsState.success([])) .eraseToAnyPublisher()
代码问题分析
collect()位置错误:你把collect()放在了单个singleIngredient的flatMap内部,这会导致每个Ingredient对应的请求完成后立刻收集并转换为PageDetailsState,而非等所有Ingredient的请求都结束后统一收集结果。- 嵌套
flatMap逻辑混乱:外层已经将ingredients数组拆分为单个Ingredient的流,内层又做了不必要的flatMap,导致流的层级混乱,无法实现“统一收集所有结果”的目标。
正确实现思路与代码
核心逻辑步骤
- 将数组流拆分为单个元素的流;
- 对每个元素发起多个API请求,等待所有请求完成后合并原元素与请求结果;
- 收集所有处理后的新对象,统一发布最终结果。
通用场景示例代码
// 假设基础类型定义 struct Object { let id: String // 其他属性 } struct ApiResult1 { /* API1返回类型 */ } struct ApiResult2 { /* API2返回类型 */ } struct ApiResult3 { /* API3返回类型 */ } struct NewObject { let original: Object let result1: ApiResult1 let result2: ApiResult2 let result3: ApiResult3 } // 初始发布者(你已有的AnyPublisher<[Object], Error>) let initialPublisher: AnyPublisher<[Object], Error> = getDataProvider() let finalPublisher = initialPublisher // 1. 把数组拆成单个Object的流 .flatMap { objects -> AnyPublisher<Object, Error> in Publishers.Sequence(sequence: objects) .setFailureType(to: Error.self) .eraseToAnyPublisher() } // 2. 对每个Object发起3个API请求,等待全部完成后合并结果 .flatMap { object -> AnyPublisher<NewObject, Error> in let api1 = dataProvider.fetchApi1(for: object.id) let api2 = dataProvider.fetchApi2(for: object.id) let api3 = dataProvider.fetchApi3(for: object.id) // 用Zip3组合三个请求,必须全部完成才会触发后续逻辑 return Publishers.Zip3(api1, api2, api3) .map { result1, result2, result3 in NewObject(original: object, result1: result1, result2: result2, result3: result3) } .eraseToAnyPublisher() } // 3. 收集所有处理后的NewObject,转换成数组发布 .collect() .replaceError(with: []) .eraseToAnyPublisher()
针对你提供的FoodDetail场景的修正代码
let foodDetails = input.appear // 获取ingredients数组 .flatMap { [unowned self] in self.dataProvider.ingredients(for: food.id) } // 拆成单个Ingredient的流 .flatMap { ingredients -> AnyPublisher<Ingredient, Error> in Publishers.Sequence(sequence: ingredients) .setFailureType(to: Error.self) .eraseToAnyPublisher() } // 对每个Ingredient获取variants并创建DetailPageObject .flatMap { [unowned self] singleIngredient -> AnyPublisher<DetailPageObject, Error> in self.dataProvider.variants(for: food.id, ingredientName: singleIngredient.id) .map { variants in var detailObj = DetailPageObject(ingredient: singleIngredient) detailObj.variants = variants return detailObj } .eraseToAnyPublisher() } // 收集所有DetailPageObject,统一转换成PageDetailsState .collect() .map { PageDetailsState.success($0) } .replaceError(with: PageDetailsState.success([])) .eraseToAnyPublisher()
关键修正点
- 将
collect()移到最外层流中,确保等待所有Ingredient的请求完成后再统一收集结果; - 移除不必要的内层
flatMap,直接用map转换variants为DetailPageObject,简化流的层级; - 保证每个
Ingredient的处理逻辑独立,最终统一输出合并后的状态。
内容的提问来源于stack exchange,提问作者wolverine
相关产品推荐
相关产品推荐

