Last active
December 14, 2025 14:52
-
-
Save PrintNow/6edd6926b36a618a26aca49aba764a43 to your computer and use it in GitHub Desktop.
一个极简的批量请求封装(DataLoader 风格) 特点: - 不依赖任何第三方库 - 自动合并同一事件循环内的请求 - 自动去重(同一个 key 只请求一次) - 对调用方保持 Promise 语义 适用场景: - React 多组件并发请求 - 减少频繁的小请求 - 测试环境 / mock 环境
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 具体使用示例看评论区:https://gist.github.com/PrintNow/6edd6926b36a618a26aca49aba764a43?permalink_comment_id=5904725#gistcomment-5904725 | |
| /** | |
| * createBatchFetcher | |
| * | |
| * 一个极简的批量请求封装(DataLoader 风格) | |
| * | |
| * 特点: | |
| * - 不依赖任何第三方库 | |
| * - 自动合并同一事件循环内的请求 | |
| * - 自动去重(同一个 key 只请求一次) | |
| * - 对调用方保持 Promise 语义 | |
| * | |
| * 适用场景: | |
| * - React 多组件并发请求 | |
| * - 减少频繁的小请求 | |
| * - 测试环境 / mock 环境 | |
| */ | |
| /** | |
| * 创建一个批量请求函数 | |
| * | |
| * @param {Function} batchFetch | |
| * 一个批量请求函数,接收 key 数组,返回 Promise | |
| * Promise resolve 的结果必须是: | |
| * { [key]: value } | |
| * | |
| * @returns {Function} | |
| * fetchByKey(key) => Promise<value> | |
| */ | |
| export function createBatchFetcher(batchFetch) { | |
| if (typeof batchFetch !== 'function') { | |
| throw new Error('createBatchFetcher 必须传入 batchFetch 函数'); | |
| } | |
| // ========================== | |
| // 内部状态(每个 fetcher 独立) | |
| // ========================== | |
| const pendingMap = new Map(); | |
| /** | |
| * key => { | |
| * promise: Promise, | |
| * resolve: Function, | |
| * reject: Function | |
| * } | |
| */ | |
| // 是否已经安排了批量请求 | |
| let scheduled = false; | |
| // ========================== | |
| // 调度批量请求(microtask) | |
| // ========================== | |
| function scheduleFlush() { | |
| // 已经安排过批量请求,直接返回 | |
| if (scheduled) return; | |
| scheduled = true; | |
| // 使用 microtask,保证同一 tick 内的调用被合并 | |
| Promise.resolve().then(async () => { | |
| scheduled = false; | |
| if (pendingMap.size === 0) return; | |
| // 快照当前批次 | |
| const batch = new Map(pendingMap); | |
| pendingMap.clear(); | |
| const keys = Array.from(batch.keys()); | |
| try { | |
| const result = await batchFetch(keys); | |
| /** | |
| * 期望 batchFetch 返回的数据结构: | |
| * { | |
| * [key]: value | |
| * } | |
| */ | |
| keys.forEach(key => { | |
| const handler = batch.get(key); | |
| if (!handler) return; | |
| if (result && key in result) { | |
| handler.resolve(result[key]); | |
| } else { | |
| handler.reject( | |
| new Error(`批量结果中缺少 key: ${String(key)}`) | |
| ); | |
| } | |
| }); | |
| } catch (err) { | |
| // 批量请求整体失败,所有 Promise 一起 reject | |
| batch.forEach(({ reject }) => reject(err)); | |
| } | |
| }); | |
| } | |
| // ========================== | |
| // 对外暴露的单条请求函数 | |
| // ========================== | |
| return function fetchByKey(key) { | |
| // 去重:同一个 key 在一个批次中只创建一个 Promise | |
| if (pendingMap.has(key)) { | |
| return pendingMap.get(key).promise; | |
| } | |
| let resolve; | |
| let reject; | |
| const promise = new Promise((res, rej) => { | |
| resolve = res; | |
| reject = rej; | |
| }); | |
| pendingMap.set(key, { promise, resolve, reject }); | |
| // 安排批量请求 | |
| scheduleFlush(); | |
| return promise; | |
| }; | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
使用示例 1:真实网络请求(fetch)
使用示例 2:测试 / Mock(setTimeout)
适合在 单元测试 / 本地调试 / 无后端环境 使用。
设计说明
createBatchFetcher实例是相互独立的