Service Worker自身加载时fetch事件未触发的原因及预获取方法问询
首先,你遇到的这个情况不是Bug,而是浏览器针对Service Worker的内置行为限制:当浏览器自动检查Service Worker更新(或者你主动调用registration.update())时,发起的sw.js请求会绕过当前激活的Service Worker,不会触发它的fetch事件。这么设计的核心原因是避免安全风险——防止旧的Service Worker篡改或拦截新的Service Worker脚本,确保浏览器能获取到真实的最新版本。
为什么之前可能可行?
有可能是之前的测试场景不同:比如你手动在页面里调用fetch('/sw.js')(就像你HTML代码里的runFetch函数),这种页面发起的请求会被Service Worker的fetch事件捕获,但浏览器自动触发的Service Worker更新请求是走的特殊通道,不会经过当前SW的拦截逻辑。
实现需求的可行方案
你的核心需求是“在新Service Worker加载前/时,让旧SW能获取到它的内容”,可以通过以下两种方式实现:
方案1:在页面端监听更新事件,传递新SW内容给旧SW
浏览器的Service Worker注册对象提供了updatefound事件,当发现有新的SW版本时,你可以在页面JS中获取新SW的脚本内容,然后通过postMessage发送给当前激活的旧SW。
修改你的HTML代码如下:
<html> <body> <h1>hello</h1> <script src="test.js"></script> <button onclick="runFetch()">fetch</button> <h2 id="swVersion"></h2> <script> async function registerSW() { const registration = await navigator.serviceWorker.register('/sw.js'); // 监听SW更新事件 registration.addEventListener('updatefound', () => { const newSW = registration.installing; // 当新SW下载完成后,获取其脚本内容 newSW.addEventListener('statechange', async () => { if (newSW.state === 'installed') { // 主动请求最新的sw.js(确保不缓存) const response = await fetch('/sw.js', { cache: 'no-store' }); const newSWContent = await response.text(); // 获取当前激活的旧SW const activeSW = registration.active; if (activeSW) { activeSW.postMessage({ type: 'NEW_SW_CONTENT', content: newSWContent, version: extractVersion(newSWContent) // 自己实现提取版本的函数 }); } } }); }); // 主动触发一次更新检查 await registration.update(); } // 从SW脚本中提取版本号的辅助函数 function extractVersion(swContent) { const match = swContent.match(/const version = (\d+);/); return match ? parseInt(match[1]) : null; } registerSW(); async function runFetch() { console.log(await fetch('/sw.js')); } </script> </body> </html>
然后在你的Service Worker中添加message事件监听,接收页面传递的新SW内容:
const version = 1; console.log(`SW with v${version} executed`); async function onFetch(event) { console.log(`fetching: ${event.request.url} with v${version}`); return await fetch(event.request); } async function onInstall() { console.log(`sw v${version} install`); await self.skipWaiting(); } async function onActivate() { console.log(`sw v${version} activate`); await self.clients.claim(); } // 新增监听页面发来的消息 async function onMessage(event) { if (event.data.type === 'NEW_SW_CONTENT') { console.log(`Old SW v${version} received new SW content, version: ${event.data.version}`); // 这里可以处理新SW的内容,比如检查版本或特定逻辑 // console.log('New SW content:', event.data.content); } } self.addEventListener('fetch', event => event.respondWith(onFetch(event))); self.addEventListener('install', event => event.waitUntil(onInstall())); self.addEventListener('activate', event => event.waitUntil(onActivate())); self.addEventListener('message', event => onMessage(event));
方案2:旧SW主动定时检查新SW版本
如果不需要严格在“加载前”获取,也可以让旧SW在激活后,定时发起对sw.js的请求(带上cache: 'no-store'确保拿到最新内容),自行检查版本变化:
async function checkForNewSW() { try { const response = await fetch('/sw.js', { cache: 'no-store' }); const content = await response.text(); const match = content.match(/const version = (\d+);/); if (match) { const newVersion = parseInt(match[1]); if (newVersion > version) { console.log(`Old SW v${version} detected new version v${newVersion}`); // 这里可以执行你的检查逻辑 } } } catch (err) { console.error('Failed to check for new SW:', err); } } // 在activate事件中启动定时检查 async function onActivate() { console.log(`sw v${version} activate`); await self.clients.claim(); // 每5分钟检查一次 setInterval(checkForNewSW, 5 * 60 * 1000); }
关键注意事项
- 浏览器自动的SW更新请求永远不会经过当前SW的
fetch事件,这是硬限制,无法绕过。 - 无论哪种方案,获取新SW内容时一定要添加
cache: 'no-store',避免拿到缓存的旧版本。 - 如果你只是需要检查版本号,也可以把版本号放在单独的
version.json文件里,让SW和页面都去请求这个文件,这样更轻量,不需要解析整个SW脚本。
内容的提问来源于stack exchange,提问作者Me7e0r

