You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的核心问题:

  1. 处理子Block时,未等待该子Block的深层子节点加载完成就直接返回父Block,导致嵌套层级数据缺失
  2. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 13:56:04