Service Worker fetch重入:如何避免相同REST请求重复发送
Service Worker REST 缓存并发重复请求优化方案
核心问题根因
原有「网络响应即缓存」的实现逻辑中,缓存的写入时机为网络请求拿到响应之后。当短时间内连续发起多个相同请求时,所有请求检测到的已落盘缓存状态都是一致的(都未写入),因此会各自发起网络请求,造成冗余调用。
优化核心思路
- 新增内存级的进行中请求映射表,在网络请求尚未完成、未写入磁盘缓存的阶段,将对应请求的Promise实例存入映射表,相同的并发请求直接复用该Promise实例,无需重复发起网络调用。
实现细节
- 仅对需要走缓存策略的幂等REST请求(默认GET方法)生效,非幂等请求直接放行,避免写操作被错误复用
- 请求结果查找优先级:磁盘已落盘缓存 > 内存中pending的请求映射 > 新发起网络请求
- 所有pending请求在完成(成功/失败)后,必须从映射表中清除对应条目,避免内存泄漏或异常状态阻塞后续请求
优化后核心实现代码
// 全局维护进行中请求的映射关系:key为请求唯一标识,value为未完成的请求Promise const pendingRequestMap = new Map(); // 生成请求唯一匹配key:匹配请求方法+完整URL,避免误匹配 function generateRequestKey(request) { return `${request.method}-${request.url}`; } self.addEventListener('fetch', function (event) { // 非GET请求不参与缓存复用逻辑 if (event.request.method !== 'GET') { return; } event.respondWith( caches.open('mysite-dynamic').then((cache) => { return cache.match(event.request).then((cacheRes) => { // 磁盘缓存命中直接返回 if (cacheRes) return cacheRes; const requestKey = generateRequestKey(event.request); // 相同请求正在进行中,直接复用pending的Promise if (pendingRequestMap.has(requestKey)) { return pendingRequestMap.get(requestKey); } // 无可用缓存、无进行中请求,才发起新网络请求 const fetchTask = fetch(event.request).then((networkRes) => { // 仅缓存成功的响应 if (networkRes.status === 200) { cache.put(event.request, networkRes.clone()); } // 请求完成后清除pending记录 pendingRequestMap.delete(requestKey); return networkRes; }).catch((err) => { // 请求失败也要清除pending记录,避免阻塞后续相同请求 pendingRequestMap.delete(requestKey); throw err; }); // 将发起的请求存入映射表 pendingRequestMap.set(requestKey, fetchTask); return fetchTask; }); }) ); });
注意事项
- 生成请求匹配key时,需根据实际业务的请求规则调整,比如带动态鉴权头、查询参数的请求,需将影响响应结果的参数纳入key计算,避免不同内容的请求被错误复用
- Service Worker存在被浏览器回收重启的机制,内存中的pending映射表会在重启后清空,该场景下原有并发连接已随SW终止断开,不会出现重复请求问题,符合预期
- 如果需要支持带自定义请求头的缓存匹配,可在
cache.match时传入匹配参数,兼容跨域、自定义头的场景
内容的提问来源于stack exchange,提问作者vivi
相关产品推荐
相关产品推荐

