Swift Combine处理Notion API嵌套分页Block网络请求
使用Notion API获取嵌套分页Block数据的问题
我正在使用Notion API,数据以Block对象形式返回。每个Block可能存在分页,且可能包含由Block组成的子节点数组。
Block结构定义
struct Block { var id: String var data: [Data] var hasMore: Bool var hasChildren: Bool var childrenId: String var nextPageId: String var children: [Block]? = nil // 用于分页累加数据 mutating func accumulator(newRes: BlockResponse) -> BlockResponse { let combinedArr = self.results + newRes.results var combinedRes = newRes combinedRes.results = combinedArr self = combinedRes return self } }
需求
- 传入一组Block ID
- 获取每个ID对应的完整Block数据,包括所有分页数据和嵌套子节点数据
注意事项
- 分页数据需合并到原Block的
data字段中 - 子节点数据需存入父Block的
children字段 - 每个子Block也可能存在分页和子节点
当前问题
现有代码已能处理顶层Block的分页和一级子节点,但无法正确获取嵌套且带分页的子节点数据,只能返回一级深层的子节点,核心问题出在loadChildData函数中。
当前代码
func getAllBlockData() -> AnyPublisher<[Block], Never> { return loadAllBlockIds() // 返回一组ID列表 .map { $0.results.map { $0 } } // 从结果中提取ID .flatMap(\.publisher) .flatMap(loadBlockData) .collect() .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } func loadBlockData(forId id: String) -> AnyPublisher<Block, Never> { return loadBlock(id: id) .flatMap(getMoreBlockData) .flatMap(getChildData) .eraseToAnyPublisher() } func loadBlock(forId id: String) -> AnyPublisher<Block, Never> { // 获取单页Block数据的基础实现 } func getMoreBlockData(forBlock block: Block) -> AnyPublisher<Block, Never> { if block.hasMore { return loadMoreBlockData(forBlock: block) } return Just(block).eraseToAnyPublisher() } func getChildData(forBlock block: Block) -> AnyPublisher<Block, Never> { if block.hasChildren { return loadChildData(forBlock: block) } return Just(block).eraseToAnyPublisher() } // 递归调用累加分页数据 func loadMoreBlockData(forBlock block: Block) -> AnyPublisher<Block, Never> { func doRequest(forId id: String, accumulator: Block) -> AnyPublisher<Block, Never> { return loadBlock(id: id) .flatMap { blockRes -> AnyPublisher<Block, Never> in var mutableAccumulator = accumulator if blockRes.hasMore { return doRequest(forId: blockRes.nextPageId, accumulator: mutableAccumulator.accumulator(newRes: blockRes)) } return Just(mutableAccumulator.accumulator(newRes: blockRes)).eraseToAnyPublisher() } .eraseToAnyPublisher() } return doRequest(forId: block.nextPageId, accumulator: block) } // 存在问题的函数:无法处理嵌套子Block的分页和深层子节点 func loadChildData(forBlock block: Block) -> AnyPublisher<Block, Never> { var mutableBlock = block func doRequest(forId id: String) -> AnyPublisher<Block, Never> { return loadBlock(id: id) .flatMap(getMoreBlockData) .flatMap({ childRes -> AnyPublisher<Block, Never> in if mutableBlock.children != nil { mutableBlock.children?.append(childRes) } else { mutableBlock.children = [childRes] } return Just(childRes).eraseToAnyPublisher() }) .flatMap(getChildData) .flatMap { _ -> AnyPublisher<Block, Never> in return Just(mutableBlock).eraseToAnyPublisher() } .eraseToAnyPublisher() } return doRequest(forId: block.childrenId) }
问题分析
loadChildData的核心问题:
- 处理子Block时,未等待该子Block的深层子节点加载完成就直接返回父Block,导致嵌套层级数据缺失
- 在Combine异步流中直接修改
mutableBlock的children字段,存在时序问题,无法保证所有子节点加载完成后再返回父Block
修复方案
修改loadChildData,确保每个子Block先完成自身的分页和深层子节点加载,再合并到父Block的children中:
func loadChildData(forBlock block: Block) -> AnyPublisher<Block, Never> { // 1. 先加载子Block的所有分页数据,合并为完整的子Block列表 loadBlock(id: block.childrenId) .flatMap { initialChildPage -> AnyPublisher<[Block], Never> in func loadAllChildPages(currentPage: Block, accumulated: [Block]) -> AnyPublisher<[Block], Never> { // 合并当前页的子Block到累计列表(需根据实际data结构调整) var newAccumulated = accumulated + currentPage.data if currentPage.hasMore { return loadBlock(id: currentPage.nextPageId) .flatMap { nextPage in loadAllChildPages(currentPage: nextPage, accumulated: newAccumulated) } } else { return Just(newAccumulated).eraseToAnyPublisher() } } return loadAllChildPages(currentPage: initialChildPage, accumulated: []) } // 2. 对每个子Block,递归加载其自身的分页和深层子节点 .flatMap { childBlocks -> AnyPublisher<[Block], Never> in Publishers.Sequence(sequence: childBlocks) .flatMap { childBlock in loadBlockData(forId: childBlock.id) // 复用loadBlockData自动处理子Block的完整数据 } .collect() } // 3. 将加载完成的所有子Block赋值给父Block,返回完整的父Block .map { loadedChildren -> Block in var mutableBlock = block mutableBlock.children = loadedChildren return mutableBlock } .eraseToAnyPublisher() }
关键调整点
- 先合并子Block的所有分页数据,得到完整的子Block列表
- 复用
loadBlockData函数,自动处理每个子Block的分页和深层子节点加载 - 等待所有子Block加载完成后,再赋值给父Block的
children字段,保证数据完整性
内容的提问来源于stack exchange,提问作者connorvo
相关产品推荐
相关产品推荐

