fetch本身不是类,无法直接实例化单例,但可通过Map缓存请求Promise实现逻辑单例:相同URL+method+body的请求未完成时复用已有Promise,避免重复发起。

JavaScript 中 fetch 本身不是类,不能直接用传统单例模式“实例化”,但可以通过封装 + 状态管理来实现**逻辑上的单例请求控制**,核心目标是:同一请求(相同 URL + 方法 + 参数)在未完成前,后续调用直接返回已有 Promise,避免重复发起网络请求。
用 Map 缓存进行请求去重
这是最常用、实用的方式。用一个全局 Map 存储正在执行的请求,以请求标识(如序列化的 URL + method + body)为 key,Promise 为 value。
示例代码:
const pendingRequests = new Map();
function createRequestKey(input, init = {}) {
const url = typeof input === 'string' ? input : input.toString();
const { method = 'GET', body } = init;
// 简单键生成(生产环境建议用更健壮的序列化,如 JSON.stringify({url, method, body}))
return `${method.toUpperCase()}|${url}|${body || ''}`;
}
async function fetchOnce(input, init = {}) {
const key = createRequestKey(input, init);
if (pendingRequests.has(key)) {
return pendingRequests.get(key); // 返回已有 Promise
}
const promise = fetch(input, init)
.then(res => {
pendingRequests.delete(key); // 成功后清理
return res;
})
.catch(err => {
pendingRequests.delete(key); // 失败也清理
throw err;
});
pendingRequests.set(key, promise);
return promise;
}
支持取消或手动清理的增强版
实际业务中可能需要主动取消某请求(比如用户离开页面),或清空全部缓存。可扩展如下:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- 给每个请求附加 AbortController,暴露
abort()方法 - 导出
clearPending(key)或clearAll()辅助函数 - 对 GET 请求默认去重,POST/PUT 等非幂等方法谨慎使用(除非你明确知道可以安全复用)
配合 React 等框架时的注意事项
在组件内调用时,需注意生命周期:
- 避免在每次渲染都新建请求(应放在事件回调或 useEffect 依赖稳定处)
- 组件卸载时,若请求还在 pending,可调用
abort()并从 Map 中移除 - 不要把
fetchOnce直接写在 render 函数里,否则会无限触发
替代思路:用 AbortSignal 控制并发而非去重
如果目标不是“完全阻止重复”,而是“只保留最后一次”,可用 AbortController 中断前序请求:
let currentController = null;
async function fetchLatest(input, init = {}) {
if (currentController) currentController.abort();
currentController = new AbortController();
try {
const res = await fetch(input, {
...init,
signal: currentController.signal
});
currentController = null;
return res;
} catch (err) {
if (err.name !== 'AbortError') throw err;
}
}
这种方式适合搜索输入实时请求等场景,但不等同于“单例”(不会复用响应,只是中断旧请求)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










