Service Worker · API
FetchEvent 与请求路由
发布于
受控客户端发出的每一个导航请求与子资源请求,都会在 service worker 中派发一个 FetchEvent;调用 event.respondWith() 可以用任意 Response 取代网络响应,不调用则请求原样走网络。该接口自 Chrome 40、Edge 17、Firefox 44、Safari 11.1 起可用(BCD api.FetchEvent),后来加入的 preloadResponse 与 handled 在各自条目中说明。
self.addEventListener('fetch', (event) => { event.respondWith(responsePromise); // 可选:接管响应 event.waitUntil(promise); // 可选:为副作用延长 worker 存活时间});respondWith() 必须在处理函数内同步调用;处理函数返回后,浏览器已经决定是否走网络。waitUntil() 则可以在事件仍在处理期间的任何时刻调用,包括在 respondWith() 的 Promise 链内部。
事件暴露请求对象与客户端标识;两个方法分别控制响应与 worker 的生命周期。
| 成员 | 类型 | 说明 |
|---|---|---|
request |
Request |
被拦截的请求。url、method、headers、mode、destination、credentials 是路由时用到的属性。 |
respondWith(r) |
void |
接受 Response 或 Promise<Response>。Promise 的落定结果就是客户端收到的响应。 |
waitUntil(p) |
void |
把事件生命周期延长到 p 落定,这样在 respondWith() 之后发起的缓存写入不会因 worker 被终止而中断。 |
clientId |
string |
发出请求的客户端 id。导航请求时为空,因为正在导航的客户端尚不存在。 |
resultingClientId |
string |
导航将要创建的客户端 id。子资源请求时为空。 |
replacesClientId |
string |
被导航取代的客户端 id(仅同窗口导航)。 |
handled |
Promise<void> |
响应交付给客户端后兑现;事件以网络错误结束时拒绝(BCD api.FetchEvent.handled)。 |
preloadResponse |
Promise<Response | undefined> |
导航预加载的响应;预加载未执行时为 undefined,见 NavigationPreloadManager 条目。 |
request.destination 是最可靠的路由键:导航为 'document',另有 'script'、'style'、'image'、'font',而 fetch() 与 XMLHttpRequest 调用为 ''。对顶层页面,request.mode === 'navigate' 与 destination === 'document' 等价,但前者也匹配 iframe,后者对 iframe 为 'iframe'。
respondWith() 同步抛错;响应侧的失败则以网络错误交给客户端,而不是 worker 内的异常。
InvalidStateError:在处理函数返回之后才调用respondWith(),或对同一事件第二次调用。- 客户端收到网络错误:传给
respondWith()的 Promise 被拒绝,或兑现为一个不是Response的值。Chromium 在 worker 控制台记录The FetchEvent for "<url>" resulted in a network error response: an object that was not a Response was passed to respondWith().,页面侧则看到TypeError: Failed to fetch(导航请求则是错误页)。 - 客户端收到网络错误:
Response的 body 已被使用,例如未clone()就交给了cache.put()。 new Request(event.request, init)的TypeError:请求是导航(mode: 'navigate')且init非空;导航请求需要改头时,改为从event.request.url构造新的Request。
下面的处理函数都写成只有匹配的分支才调用 respondWith();其他请求全部放行给网络。
按 destination 与 URL 前缀路由
Section titled “按 destination 与 URL 前缀路由”图片走缓存优先的辅助函数,API 调用绕过缓存,其余交给浏览器。
self.addEventListener('fetch', (event) => { const url = new URL(event.request.url); if (url.origin !== self.location.origin) return; // 第三方:不拦截
if (event.request.destination === 'image') { event.respondWith(cacheFirst(event.request, 'images-v1')); } else if (url.pathname.startsWith('/api/')) { event.respondWith(fetch(event.request)); // 显式的仅网络 }});
async function cacheFirst(request, cacheName) { const cache = await caches.open(cacheName); const cached = await cache.match(request); if (cached) return cached; const response = await fetch(request); if (response.ok) cache.put(request, response.clone()); return response;}即使是仅网络的路由,对其他源提前返回也很重要:一旦调用了 respondWith(),整个响应(包括 CORS 行为与 opaque 响应)就由 worker 负责。
用 waitUntil() 为导航返回应用壳
Section titled “用 waitUntil() 为导航返回应用壳”单页应用对每次导航都用缓存的壳作答,并在后台刷新该壳,同时让 worker 为这次刷新保持存活。
self.addEventListener('fetch', (event) => { if (event.request.mode !== 'navigate') return; event.respondWith( caches.match('/index.html').then((cached) => cached ?? fetch(event.request)) ); event.waitUntil( fetch('/index.html').then((fresh) => fresh.ok ? caches.open('shell-v1').then((c) => c.put('/index.html', fresh)) : undefined ).catch(() => {}) );});没有 waitUntil() 时,浏览器可能在 respondWith() 一落定就终止 worker,丢掉后台刷新。catch 则避免离线刷新变成未处理的拒绝。
检测 service worker 支持,并在没有它时让请求照常工作
Section titled “检测 service worker 支持,并在没有它时让请求照常工作”没有受控 worker 的页面(首次访问、非安全源、强制刷新)也必须能加载。按条件注册,并且不要让页面代码依赖拦截。
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').catch((err) => { console.warn('Service worker registration failed; running without offline support', err); });}// 页面代码在两种情况下都以同样方式调用 fetch('/api/items'):// worker 只是一层优化,没有 worker 接管时请求同样成立。在 worker 控制页面之前 navigator.serviceWorker.controller 为 null,Chrome 与 Firefox 中强制刷新(Shift+Reload)之后也是这个状态;读取它的代码必须处理 null。
- Service Workers specification: FetchEvent interface(w3c.github.io)
- FetchEvent: respondWith() method(developer.mozilla.org)
- FetchEvent(developer.mozilla.org)
- Service worker 缓存策略
- Cache API
- NavigationPreloadManager
- 离线兜底
规范
| 规范 | 状态 |
|---|---|
| 无。 | |