navigationPreload没生效的根本原因是未在install事件中用event.waitUntil()包裹enable()调用;fetch中需用try/catch处理preloadResponse可能的reject,并以caches.match或fetch为fallback。

navigationPreload 启用后为什么没生效?
根本原因通常是 navigationPreload 没在 Service Worker 安装阶段正确启用。它必须在 install 事件中调用 event.waitUntil() 包裹的 self.registration.navigationPreload.enable(),否则后续的 fetch 事件里查 event.preloadResponse 永远是 undefined。
常见错误包括:
- 在
activate或fetch中调用enable()(无效) - 没用
event.waitUntil()包裹,导致启用被忽略 - 启用了但没检查浏览器支持(
'navigationPreload' in self.registration)
正确写法示例:
self.addEventListener('install', event => {
event.waitUntil(
self.registration.navigationPreload.enable()
);
});
fetch 事件中如何安全读取 preloadResponse?
event.preloadResponse 是一个 Promise,但它可能 reject(比如网络超时、CORS 失败、preload 被禁用),不能直接 await 后使用。必须用 Promise.race() 或 try/catch 控制 fallback 行为,否则整个 fetch 流程会卡住或报错。
立即学习“前端免费学习笔记(深入)”;
典型处理模式:
- 始终提供 fallback:用
fetch(event.request)作为兜底 - 避免 race 条件:不要同时 await
preloadResponse和fetch()再比谁快——应让 preload 作为可选加速路径 - 注意请求体限制:preload 只对 navigation 类型请求触发(
event.request.mode === 'navigate'),且不带 body(如 POST 不适用)
简明示例:
self.addEventListener('fetch', event => {
if (event.request.mode !== 'navigate') return;
event.respondWith(
(async () => {
try {
const preloadResp = await event.preloadResponse;
if (preloadResp && preloadResp.ok) return preloadResp;
} catch (e) {
// 忽略 preload 失败,继续走缓存或网络
}
return caches.match(event.request).then(r => r || fetch(event.request));
})()
);
});
等待 activate 阶段能否发起 preload 请求?
不能。Service Worker 的 navigationPreload 机制本身不依赖当前 SW 是否已 activate —— 它由浏览器在 install 成功后、新 SW 还在 waiting 状态时,就为后续导航请求提前发起网络拉取。也就是说,只要你在 install 阶段启用了它,即使新 SW 还没 activate,浏览器也会悄悄发请求,并把响应缓存在内部 preload cache 中,等该 SW 激活后的 fetch 事件里通过 event.preloadResponse 暴露出来。
关键点:
- preload 请求由浏览器发起,不是 SW 主动 fetch
- waiting 状态不影响 preload 行为,但会影响你是否能拿到响应(只有 activate 后的 fetch 事件才能访问
event.preloadResponse) - 如果用户强制 skipWaiting,preload 响应仍可用;但如果旧 SW 拦截了请求并返回了响应,新 SW 就收不到 preload 结果
Chrome DevTools 里怎么验证 preload 是否工作?
打开 Application → Service Workers → 勾选 “Update on reload” 和 “Offline”,再刷新页面。重点看 Network 面板:
- 找类型为
document的请求,右键 → “Copy” → “Copy as fetch”,粘贴到控制台执行,确认是否带service-worker-navigation-preload: trueheader - 在 fetch 事件断点中打印
event.preloadResponse,看它是 pending 还是 resolved - 禁用 preload 后对比首屏加载时间(尤其弱网下),差异明显说明生效
容易忽略的是:preload 响应默认不进 caches,也不会触发 cache.put(),它只供当前 fetch 事件一次性消费。想复用得自己 caches.open().put() 存进去。


















