cache.addAll() 是原子操作,任一资源失败(如404、CORS问题、非2xx状态等)则全部不缓存,导致Service Worker卡在installing状态;必须用event.waitUntil()包裹整个Promise链,使用绝对路径或完整URL,并配套更新缓存名与清理旧缓存。

cache.addAll() 为什么一失败就全不缓存
因为它是原子操作:数组里任意一个 URL 返回 404、跨域但没配 CORS、响应状态不是 2xx(比如 302、500)、甚至请求方法不是 GET,整个 cache.addAll() 就直接 reject,已处理的资源一个都不会进缓存,install 事件中断,Service Worker 卡在 installing 状态,后续 fetch 事件压根不会触发。
常见踩坑点:
- 忘了检查服务器返回的
Cache-Control: no-store—— 比如 SSR 渲染的/页面带这个头,cache.addAll(['/'])会静默失败 - 路径写成
'./app.js'或'js/app.js',但在 SW 脚本中相对路径解析基准是 SW 文件本身,不是 HTML 页面,容易 404 - 混用协议或域名,比如页面是
https://example.com,却在列表里写'http://cdn.example.com/logo.png',没开 CORS 就失败
路径怎么写才保险
必须用浏览器能正确 fetch 的 URL 形式。SW 中不支持 document.baseURI 那套逻辑,只认绝对路径('/index.html')或完整 URL('https://example.com/app.js'),相对路径('app.js')只有在 SW 和目标资源同目录时才可靠。
推荐做法:
立即学习“前端免费学习笔记(深入)”;
- 静态资源统一走根相对路径:
'/styles/main.css'、'/scripts/app.js',前提是你的服务支持这些路径可访问 - HTML 文件必须显式列出:
'/'或'/index.html',它内部引用的 JS/CSS 不会自动递归抓取 - 关键图片、字体等同步依赖也得列进去,否则离线时 HTML 加载了,子请求 404
- 避免带查询参数的路径(如
'/app.js?v=1.2.3'),除非你确定每次构建都更新它;否则建议用构建时注入版本号的缓存名来隔离
event.waitUntil() 不包就等于没写
caches.open() 和 cache.addAll() 都返回 Promise,如果不被 event.waitUntil() 包裹,浏览器会认为 install 事件立刻完成,异步缓存操作可能被中断或丢弃,结果就是缓存看起来“没生效”。
正确写法只有一种结构:
self.addEventListener('install', event => {
event.waitUntil(
caches.open('static-v2').then(cache =>
cache.addAll([
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js'
])
)
);
});
注意点:
- 不能把
event.waitUntil()放在caches.open()外层就完事,必须包裹整个链式 Promise - 不要在
addAll()后加.catch()吞掉错误 —— 它失败本就应该让 install 失败,旧 SW 继续运行更安全 - 想跳过 waiting 阶段(比如立即激活新 SW),可在
waitUntil链末尾加self.skipWaiting(),但要确保 runtime caching 逻辑已就位,否则可能引发资源不一致
缓存名和旧缓存清理必须配套做
缓存名硬编码成 'static-v1' 是自找麻烦。每次更新资源,应该生成唯一缓存名(比如 `static-${VERSION}`),并在 activate 阶段清理非当前版本的缓存。
否则会出现:
- 旧缓存残留,占用用户设备空间
- 不同版本缓存名冲突,导致
caches.open()拿到意料外的缓存实例 - activate 阶段没清理,用户可能长期用着过期的 CSS/JS
清理逻辑示例:
self.addEventListener('activate', event => {
const CURRENT_CACHE = 'static-v2';
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys
.filter(key => key !== CURRENT_CACHE)
.map(key => caches.delete(key))
)
)
);
});
真正容易被忽略的是:这个清理动作必须和 install 阶段的缓存名严格对应,且不能漏掉任何历史版本 —— 构建流程里最好自动生成并注入 CURRENT_CACHE 常量,而不是靠人肉维护。



















