如何用Combine实现Swift嵌套网络请求并行执行及错误处理
问题:Combine框架下实现元数据并行请求并处理失败场景
我需要实现这样的业务流程:
- 先调用API获取元数据列表
- 基于该列表并行调用API获取每个元数据对应的内容
- 整合所有结果,将元数据的
identifier和type字段更新到对应的Content实例中,最终返回[Content]
需求要求:
- 若第一个获取元数据的API调用失败,直接终止后续所有请求
- 第二个批量请求中,单个元数据的请求可能失败,但如果全部请求都失败,则返回错误;如果有部分成功,就返回成功的内容数组
现有结构体定义:
struct MetaData: Codable { var identifier: String? var type: String? var url: String? } struct Content: Codable { var contentId: String? // 来自metaData的identifier var type: String? // 来自metaData的type var title: String? var subTitle: String? var subscriptionValue: String? }
已实现的请求方法:
func fetchMetaData() -> AnyPublisher<[MetaData], Error> { return Future<[MetaData], Error> { promise in //network call //if success { //promise(.success([MetaData])) //} else { //promise(.failure(Error())) //} }.eraseToAnyPublisher() } func fetchContents(metaData: MetaData) -> AnyPublisher<Content, Error> { return Future<Content, Error> { promise in //network call //if success { //promise(.success(Content)) //} else { //promise(.failure(Error())) //} }.eraseToAnyPublisher() }
刚接触Combine框架,请问如何组合这两个方法,完成上述需求并正确处理各种失败场景?
解决方案
我们可以利用Combine的flatMap、Publishers.MergeMany和collect等操作符,结合错误处理逻辑来实现需求,以下是完整实现代码:
import Combine func fetchAndCombineContents() -> AnyPublisher<[Content], Error> { fetchMetaData() .flatMap { metaDataList -> AnyPublisher<[Content], Error> in // 为每个元数据创建对应的内容请求Publisher,并处理单个请求失败 let contentPublishers = metaDataList.map { metaData in fetchContents(metaData: metaData) // 将单个请求的失败转换为返回nil的成功,避免中断整个流 .catch { _ in Just(nil) } .map { content -> Content? in guard var content = content else { return nil } // 将元数据的identifier和type更新到Content实例 content.contentId = metaData.identifier content.type = metaData.type return content } } // 处理空元数据列表的情况 guard !contentPublishers.isEmpty else { return Just([]) .setFailureType(to: Error.self) .eraseToAnyPublisher() } // 并行合并所有内容请求,收集结果后处理 return Publishers.MergeMany(contentPublishers) .collect() // 收集所有请求的结果到数组 .map { contents in // 过滤掉请求失败的nil项 contents.compactMap { $0 } } .tryMap { filteredContents in // 检查是否所有请求都失败,若是则抛出错误 guard !filteredContents.isEmpty else { throw NSError(domain: "ContentFetchError", code: -1, userInfo: [NSLocalizedDescriptionKey: "所有内容请求均失败"]) } return filteredContents } .eraseToAnyPublisher() } .eraseToAnyPublisher() }
关键逻辑说明:
元数据请求失败处理:
fetchMetaData()的失败会直接向上传递,flatMap不会执行,后续所有请求都不会触发,完全符合第一个需求。单个内容请求失败处理:
通过.catch { _ in Just(nil) }将单个请求的失败转换为返回nil的成功,这样不会导致整个合并流中断,确保其他请求能正常完成。元数据字段更新:
在map操作中,将对应元数据的identifier和type赋值给Content实例,完成字段同步。全部请求失败的处理:
collect()收集所有结果后,用compactMap过滤掉nil项,再通过tryMap检查过滤后的数组是否为空。如果为空,抛出自定义错误;否则返回成功的内容数组,满足第二个需求。空元数据列表处理:
如果元数据列表为空,直接返回空数组,避免无意义的合并操作。
内容的提问来源于stack exchange,提问作者ReB
相关产品推荐
相关产品推荐

