Service Worker 能拦截 fetch 发起的同源 API 请求,但不拦截跨域请求和 XMLHttpRequest;需激活且页面受控,拦截逻辑须在 fetch 事件中显式编写,并配合 skipWaiting() 和 clients.claim() 确保生效。

Service Worker 能否拦截 fetch 发起的 API 请求
能,但仅限于同源请求,且必须在 Service Worker 已激活、页面已受控的前提下。非同源请求(如跨域 fetch)会走浏览器原生网络栈,无法被 fetch 事件捕获;而 XMLHttpRequest 同样不可拦截——Service Worker 的 fetch 事件只响应 fetch() 和 <img alt="如何用 Service Worker 拦截 API 请求并根据版本号实现资源的强制增量更新" >、<script></script> 等资源请求,不处理 XHR。
实操建议:
- 确保 API 请求全部改用
fetch(),并显式设置credentials: 'include'(如需带 cookie) - 在注册 Service Worker 前,先检查
'serviceWorker' in navigator,避免低版本 Safari 或禁用环境报错 - 不要依赖
navigator.onLine判断网络状态——它不可靠,且与 Service Worker 拦截逻辑无关
如何识别并拦截特定 API 请求(如带 v=2.1.0 的版本参数)
不能只靠 URL 字符串匹配 v=,因为参数顺序不确定、可能被编码、也可能走 POST body。更可靠的方式是:在 fetch 事件中解析 request.url 的 query string,或对关键接口路径做白名单 + 版本字段提取。
示例逻辑(在 Service Worker 中):
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// 只拦截 /api/ 开头且含 v= 参数的 GET 请求
if (url.origin === location.origin &&
url.pathname.startsWith('/api/') &&
url.searchParams.has('v') &&
event.request.method === 'GET') {
const clientVersion = url.searchParams.get('v');
const cachedVersion = caches.match('/version.json').then(r => r?.json()).then(d => d?.version);
if (clientVersion !== cachedVersion) {
// 强制跳过 cache,走网络
event.respondWith(fetch(event.request));
return;
}
}
event.respondWith(caches.match(event.request).then(r => r || fetch(event.request)));
});
注意点:
-
URL.searchParams在 Service Worker 中可用(Chrome 45+、Firefox 41+),无需 polyfill - 不要在
fetch事件中 await 异步操作(如caches.match().then(...))后才调用event.respondWith(),否则会触发“未响应”异常;应统一用Promise.resolve()包裹逻辑 - POST 请求若需版本控制,建议把版本号移到
request.headers.get('X-App-Version'),比塞在 body 里更可控
如何实现「强制增量更新」而不破坏已有缓存
所谓“强制增量”,本质是让新版资源绕过旧缓存、写入新缓存,并在下次请求时生效——不是清空所有 cache,而是按版本隔离缓存空间。典型做法是给每个 cache 命名带上版本号,比如 cache-v2.1.0,再用一个 CURRENT_CACHE_NAME 全局变量指向当前有效缓存。
关键步骤:
- 每次更新 Service Worker 时,在
install阶段打开新 cache(如cache-v2.1.0),预缓存新版 API 响应(可选)或静态资源 - 在
activate阶段,遍历caches.keys(),删除除当前版本外的所有 cache(避免磁盘爆满) - 拦截逻辑中,始终读写
CURRENT_CACHE_NAME对应的 cache,而非硬编码名称
容易踩的坑:
- 不要在
install中直接fetch所有 API 并缓存——API 响应通常带动态内容(如用户数据),不该全局缓存;应只缓存明确可复用的资源(如配置、字典、公共元数据) -
caches.delete()是异步操作,但activate事件不会等它完成;需用event.waitUntil()包裹,否则可能删不干净 - 如果 API 响应设置了
Cache-Control: no-store,即使你手动 put 进 cache,后续caches.match()仍可能返回undefined——这是浏览器策略,无法绕过
为什么 skipWaiting() 和 clients.claim() 必须配合使用
默认情况下,新注册的 Service Worker 处于 waiting 状态,直到所有旧 tab 关闭才会 activate。而 skipWaiting() 让它立刻进入 activating,clients.claim() 则让当前页面立即受控——两者缺一不可。否则,页面继续走旧 SW 的逻辑,你的版本拦截规则根本不会执行。
正确写法(在新 SW 文件顶部):
self.addEventListener('install', event => {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', event => {
event.waitUntil(
Promise.all([
self.clients.claim(),
caches.keys().then(keys =>
Promise.all(keys.map(key => {
if (!key.includes(CURRENT_CACHE_NAME)) return caches.delete(key);
}))
)
])
);
});
注意:
-
skipWaiting()应放在install里,而不是注册时调用registration.waiting?.postMessage({type: 'SKIP_WAITING'})——后者需要额外 message 事件监听,增加复杂度 -
clients.claim()必须在activate中调用,且要包裹在event.waitUntil()内,否则部分浏览器(如早期 Edge)可能不生效 - 一旦启用
skipWaiting(),就意味旧 SW 会突然被终止,其正在运行的fetch、message等事件可能中断——确保业务逻辑无强依赖旧 SW 的持久状态
skipWaiting() 和 clients.claim() 的耦合时机,这三处最容易被当成“顺手写写”而埋下线上故障。尤其当多个团队共用同一套 API 网关时,客户端传参格式稍有不一致,版本识别就会失效。

















