Service Worker · API
NavigationPreloadManager
发布于
NavigationPreloadManager 通过 registration.navigationPreload 访问,它让浏览器在启动 service worker 的同一时刻就发出导航的网络请求,而不是等 worker 启动完成再由它调用 fetch();worker 随后从 FetchEvent.preloadResponse 读取这份提前到达的响应。它自 Chrome 59、Edge 79、Firefox 99、Safari 15.4 起可用(BCD api.NavigationPreloadManager),且仅限安全上下文。
// service worker 中(或页面中经由 navigator.serviceWorker.ready)await registration.navigationPreload.enable();await registration.navigationPreload.disable();await registration.navigationPreload.setHeaderValue(value);const state = await registration.navigationPreload.getState();
// service worker 的 fetch 处理函数内const preloaded = await event.preloadResponse; // Response 或 undefined预加载是按注册保存的设置,跨 worker 重启与更新持续有效,所以只需启用一次(通常在 activate 中),无需每次启动都重新启用。
该管理器有四个方法,都返回 Promise;状态对象有两个字段。
| 成员 | 返回值 | 行为 |
|---|---|---|
enable() |
Promise<void> |
为该注册开启预加载。之后 scope 内的导航都会并行发出一个带 Service-Worker-Navigation-Preload 请求头的 GET。 |
disable() |
Promise<void> |
关闭预加载。此后每次导航的 event.preloadResponse 都兑现为 undefined。 |
setHeaderValue(value) |
Promise<void> |
设置预加载请求所带 Service-Worker-Navigation-Preload 头的值。默认为 true。 |
getState() |
Promise<{ enabled: boolean, headerValue: string }> |
报告预加载是否开启及当前的头值。 |
FetchEvent.preloadResponse |
Promise<Response | undefined> |
预加载开启时导航 GET 的预加载响应;子资源、非 GET 请求以及预加载关闭时为 undefined。 |
这个头存在的目的,是让服务器可以为预加载请求返回不同的内容(例如 worker 自己渲染壳时只返回 JSON 数据而不是完整 HTML)。按它分流的服务器必须发送 Vary: Service-Worker-Navigation-Preload,否则中间的 HTTP 缓存可能把预加载变体交给普通导航。
四个方法都以 DOMException 或 TypeError 拒绝,而不是同步抛出。
InvalidStateError:注册上没有处于 active 状态的 worker。在install中(worker 仍在安装)调用enable()会被拒绝;activate是最早的安全时机,页面侧对应的是navigator.serviceWorker.ready。setHeaderValue()的TypeError:value不是合法的 HTTP 头值(必须是不含控制字符与换行的字节串)。- 不是异常:预加载响应未被使用。处理函数返回了缓存响应且从未
await event.preloadResponse时,浏览器已经花掉了这次请求。只有需要消费它时才把 Promise 交给event.waitUntil();否则要么接受这次浪费的请求,要么对总是从缓存应答的路由关闭预加载。 - 不是异常:浏览器支持该管理器但本注册关闭了预加载时,
event.preloadResponse兑现为undefined。假定它一定是Response的代码会在第一次访问属性时抛出TypeError: Cannot read properties of undefined。
三个示例分别覆盖启用、消费与检测;第二个是每个开启了预加载的 worker 都需要的。
在 activate 中启用预加载
Section titled “在 activate 中启用预加载”等 worker 进入 active 后再启用,并把调用放在 waitUntil() 内,以免激活在设置写入之前就结束。
self.addEventListener('activate', (event) => { event.waitUntil((async () => { if (self.registration.navigationPreload) { await self.registration.navigationPreload.enable(); } })());});由于设置保存在注册上,后续不再调用 enable() 的 worker 版本仍然处于预加载开启状态;要去掉它,必须显式调用 disable()。
先用 preloadResponse,再回退到 fetch()
Section titled “先用 preloadResponse,再回退到 fetch()”处理函数优先返回缓存命中,其次是预加载响应,最后才是普通 fetch。预加载的 Promise 总会被 await,确保浏览器提前发出的请求被消费而不是浪费。
self.addEventListener('fetch', (event) => { if (event.request.mode !== 'navigate') return; event.respondWith((async () => { const cached = await caches.match(event.request); if (cached) { event.waitUntil(event.preloadResponse); // 让预加载静静完成 return cached; } const preloaded = await event.preloadResponse; if (preloaded) return preloaded; return fetch(event.request); })());});缓存命中后再 await preloadResponse 不会拖慢响应;它只是让 worker 存活到预加载落定。忽略尚未完成的预加载会让 Chrome 在页面控制台记录 The service worker navigation preload request was cancelled before 'preloadResponse' settled. If you intend to use 'preloadResponse', use waitUntil() or respondWith() to wait for the promise to settle.。
检测管理器是否存在,并在没有它时保持 fetch 处理函数正确
Section titled “检测管理器是否存在,并在没有它时保持 fetch 处理函数正确”Firefox 99 之前与 Safari 15.4 之前的注册对象上没有 navigationPreload,Chrome 58 及更早版本中 event.preloadResponse 是 undefined(而不是 Promise)。下面的写法在这三种状态下都成立。
// activate:只在管理器存在时启用self.addEventListener('activate', (event) => { event.waitUntil( self.registration.navigationPreload ? self.registration.navigationPreload.enable() : Promise.resolve() // 没有预加载:导航像以前一样等待 worker );});
// fetch:把缺失的 preloadResponse 当作"没有预加载"async function navigationResponse(event) { const preloaded = event.preloadResponse ? await event.preloadResponse : undefined; return preloaded ?? fetch(event.request);}回退的代价是每次导航都要付出 worker 的启动时间,这正是预加载要解决的问题;在决定是否改用缓存优先的应用壳(它会让预加载变得不必要)之前,先在 Performance 面板里测量 worker 的启动时间。
- Service Workers specification: NavigationPreloadManager(w3c.github.io)
- Speed up service worker with navigation preloads(developer.chrome.com)
- FetchEvent: preloadResponse property(developer.mozilla.org)
- FetchEvent 与请求路由
- Service worker 生命周期
- 导航预加载与启动性能
- Service worker 缓存策略
规范
| 规范 | 状态 |
|---|---|
| 无。 | |