如何优雅实现支持分页的REST API消费?设计模式探讨
嘿,这个场景我之前做项目的时候刚好遇到过,来跟你唠唠可行的方案~
合适的设计模式
针对这种需要自动遍历分页结果的场景,有几个设计模式特别适配:
- 迭代器模式:把分页获取数据的过程封装成一个迭代器,外部代码只需要遍历这个迭代器就能拿到所有数据,完全不用关心内部的分页逻辑(比如什么时候要传结果标识符、怎么检查剩余数量)。这种方式把数据获取和数据使用解耦,非常符合单一职责原则。
- 模板方法模式:如果你们团队有多个类似的分页API,可以用这个模式做通用封装。把固定的流程(发起请求→解析结果→检查剩余→循环请求)做成模板,把每个API独有的部分(比如构造请求参数、解析响应格式)抽成可替换的回调或者抽象方法,这样后续新增API的时候只需要实现这些差异化部分就行,复用性拉满。
- 生成器(Generator):虽然它不算严格意义上的经典设计模式,但在Python、JavaScript这类语言里用起来超级顺手。生成器可以一步步“产出”每一页的结果,既可以一次性把所有结果合并成列表,也可以惰性处理(比如边拿边处理,节省内存),代码简洁到爆炸。
递归VS更简洁的实现方式
递归确实能实现这个需求,但非常不推荐。原因很简单:如果分页数量特别多(比如几百上千页),递归会导致调用栈溢出,而且调试起来也比迭代麻烦得多。
更简洁可靠的方式是迭代式循环或者刚才说的生成器,核心逻辑就是:
- 发起初始请求,拿到第一页结果和分页标识
- 检查剩余数量,如果还有数据,就带着结果标识符发起下一轮请求
- 重复步骤2,直到剩余数量为0,把所有结果合并
示例代码
给你写两个简单的示例,方便你理解:
Python 同步版本(生成器实现)
def fetch_all_results(initial_query): # 发起初始请求,这里假设make_api_request是你封装的API调用函数 response = make_api_request(initial_query) # 产出第一页的所有结果 yield from response["items"] result_id = response["result_identifier"] remaining = response["remaining"] # 循环获取剩余数据 while remaining > 0: next_response = make_api_request(None, result_id=result_id) yield from next_response["items"] # 更新剩余数量和结果标识符 remaining = next_response["remaining"] result_id = next_response["result_identifier"] # 调用时直接转成列表,就能拿到所有合并后的结果 all_results = list(fetch_all_results(your_initial_query_params))
JavaScript 异步版本(Async Generator)
// 封装的API请求函数,假设返回Promise async function makeApiRequest(query, options = {}) { // 这里写你的API调用逻辑,比如fetch或者axios } async function* fetchAllResults(initialQuery) { let response = await makeApiRequest(initialQuery); // 产出第一页结果 yield* response.items; let resultId = response.resultIdentifier; let remaining = response.remaining; while (remaining > 0) { response = await makeApiRequest(null, { resultId }); yield* response.items; remaining = response.remaining; resultId = response.resultIdentifier; } } // 合并所有结果的函数 async function getAllResults(initialQuery) { const allItems = []; for await (const item of fetchAllResults(initialQuery)) { allItems.push(item); } return allItems; }
这种实现方式不仅代码清晰,还能灵活应对不同场景:如果数据量特别大,不想一次性加载到内存里,直接遍历生成器处理单条数据就行;如果需要全量数据,转成列表也很方便。
内容的提问来源于stack exchange,提问作者webworm
相关产品推荐
相关产品推荐

