Safari中Service Worker无法拦截首次页面加载致重复请求求助
问题描述
我编写了一个简单的Service Worker,用于拦截fetch请求并添加X-Custom-Header请求头,代码如下:
self.addEventListener('install', () => { self.skipWaiting(); }); self.addEventListener('activate', event => { event.waitUntil(self.clients.claim()); }); self.addEventListener('fetch', event => { event.respondWith(fetchWithHeader(event.request)); }); function fetchWithHeader(request) { const newHeaders = new Headers(); newHeaders.set('X-Custom-Header', `123`); return fetch(request, { headers: newHeaders }); }
我通过以下代码在http://localhost:3000上注册该Service Worker:
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js', { scope: '/', }); }
在Chrome和Firefox中,已安装Service Worker后访问http://localhost:3000,后端仅收到一次请求;但在Safari中,后端会收到两次请求。经排查发现,Safari在地址栏输入网址后,会先在Service Worker启动前发送一次请求,待Service Worker启动后再发送一次被其拦截的请求。如何避免该问题?
注:我在Mac和iPhone的不同Safari版本中均测试过,结果一致。
解决方案
1. 提前注册Service Worker,减少启动延迟
将Service Worker的注册代码放在页面<head>中,不等待DOMContentLoaded事件,让浏览器尽早加载并激活Service Worker,降低因启动慢导致的提前请求:
<head> <script> if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js', { scope: '/' }) .then(registration => { // 若已有激活的Service Worker,直接控制当前页面 if (registration.active) { registration.active.postMessage({ type: 'CLAIM' }); } }); } </script> </head>
2. 修复请求头丢失问题,优化请求逻辑
原代码创建新Headers实例时会丢弃原请求的所有头信息,这既不符合预期,也可能加剧重复请求问题。正确做法是基于原请求头添加自定义字段:
function fetchWithHeader(request) { // 基于原请求headers创建实例,保留所有原有头信息 const newHeaders = new Headers(request.headers); newHeaders.set('X-Custom-Header', '123'); // 克隆原请求并替换headers,避免修改原请求引发异常 const modifiedRequest = new Request(request, { headers: newHeaders }); return fetch(modifiedRequest); }
同时可添加缓存优先策略,Service Worker启动后直接从缓存返回资源,避免重复向后端发起请求:
self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(cachedResponse => { // 缓存有资源则直接返回 if (cachedResponse) { return cachedResponse; } // 无缓存则发起请求并缓存结果 return fetchWithHeader(event.request).then(networkResponse => { caches.open('static-cache').then(cache => { cache.put(event.request, networkResponse.clone()); }); return networkResponse; }); }) ); });
3. 启用NavigationPreload(Safari 15.4+支持)
开启NavigationPreload可让Service Worker在启动过程中预加载页面资源,避免重复请求:
self.addEventListener('activate', event => { event.waitUntil( Promise.all([ self.clients.claim(), // 启用NavigationPreload self.registration.navigationPreload.enable() ]) ); }); self.addEventListener('fetch', event => { event.respondWith( // 优先使用预加载响应 event.preloadResponse.then(preloadResponse => { if (preloadResponse) { // 给预加载响应添加自定义头 const modifiedResponse = new Response(preloadResponse.body, { headers: new Headers(preloadResponse.headers) }); modifiedResponse.headers.set('X-Custom-Header', '123'); return modifiedResponse; } // 回退到常规请求逻辑 return fetchWithHeader(event.request); }) ); });
内容的提问来源于stack exchange,提问作者SoroushNeshat
相关产品推荐
相关产品推荐

