Service Worker · API
skipWaiting() 与更新流程
发布于
浏览器抓取到的 service worker 脚本与已安装版本哪怕只差一个字节,就会在旧 worker 旁边安装新 worker,并把它保持在 waiting 状态,直到旧 worker 控制的所有页面都已关闭;self.skipWaiting() 解除这个等待,clients.claim() 则让已激活的 worker 接管它尚未控制的页面。这一流程的四个部分(skipWaiting()、clients.claim()、registration.update() 与 updateViaCache 选项)自 Chrome 41、Firefox 44、Safari 11.1、Edge 17 起可用(BCD api.ServiceWorkerGlobalScope.skipWaiting),只有 updateViaCache 较晚,自 Chrome 68、Firefox 57、Safari 11.1 起可用。
// service worker 中self.skipWaiting(); // Promise<undefined>self.clients.claim(); // Promise<undefined>
// 页面中const registration = await navigator.serviceWorker.register('/sw.js', { updateViaCache: 'none' });await registration.update(); // 以该注册兑现registration.waiting; // 没有等待中的 worker 时为 nullregistration.addEventListener('updatefound', handler);navigator.serviceWorker.addEventListener('controllerchange', handler);浏览器会在 scope 内发生导航、push 或 sync 事件触发,以及功能性事件距上次检查超过 24 小时到达时自动运行更新检查;registration.update() 按需触发同一检查。install 处理器被拒绝的 worker 不会进入 waiting,而是直接变为 redundant。
四个成员中两个不接受参数,另外两个分别接受一个注册选项和零个参数。
| 成员 | 位置 | 参数 | 作用 |
|---|---|---|---|
self.skipWaiting() |
worker | 无 | 标记该 worker 在当前 active worker 正在运行的事件结束后立即激活,而不是等它的所有客户端关闭。通常在 install 中调用。 |
self.clients.claim() |
worker | 无 | 让这个 active worker 成为 scope 内所有无控制器或由更旧 worker 控制的客户端的控制器;在这些页面中触发 controllerchange。只有 worker 处于 active 状态时才有效,因此应放在 activate 中。 |
registration.update() |
页面 | 无 | 重新抓取脚本(遵守 updateViaCache)并逐字节比较;无论是否发现新 worker 都以该注册兑现。 |
updateViaCache |
register() 选项 |
'imports'(默认)、'all' 或 'none' |
更新检查期间允许 HTTP 缓存提供哪些脚本:只有 importScripts() 的依赖、全部,或都不允许。与此选项无关,浏览器一律忽略超过 24 小时的缓存副本。 |
skipWaiting() 从不拒绝:规范规定它在 worker 激活后、或已处于 active 状态时以 undefined 兑现。其他成员可能拒绝。
clients.claim()在该 worker 不是注册的 active worker 时以InvalidStateError拒绝,例如在脚本顶层调用而 worker 仍在安装中。registration.update()在抓取的脚本加载失败或 MIME 类型不是 JavaScript 类型时以TypeError拒绝,在注册已被注销或其最新 worker 为null时以InvalidStateError拒绝。register()在updateViaCache不是三个允许值之一时以TypeError拒绝。
调用 skipWaiting() 却不重新加载受控页面,会让这些页面继续运行上一次部署的 HTML,而 worker 的预缓存里已是新部署的内容;页面稍后懒加载某个已不存在的带哈希分块时会得到 404。示例中的 controllerchange 重新加载正是为了关上这个窗口。
三个示例分别对应图中的三条路径:立即接管、询问用户后更新、按需检查。
用 skipWaiting()、claim() 与带保护的重新加载立即接管
Section titled “用 skipWaiting()、claim() 与带保护的重新加载立即接管”worker 在 install 中跳过等待、在 activate 中接管客户端;页面在 controllerchange 时重新加载一次。refreshing 标志防止 claim() 第二次触发该事件时陷入重载循环。
self.addEventListener('install', (event) => { event.waitUntil(caches.open('app-v2').then((cache) => cache.addAll(['/', '/app.js']))); self.skipWaiting();});self.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim());});
// page.jslet refreshing = false;navigator.serviceWorker.addEventListener('controllerchange', () => { if (refreshing) return; refreshing = true; location.reload();});这条路径的代价是每个打开的标签页都会在会话中途重新加载,包括用户正在填写表单的那个;它适合页面生命周期短的应用。
激活前先询问用户
Section titled “激活前先询问用户”页面监听 updatefound,等新 worker 到达 installed 后显示控件;用户点击后才向 worker 发消息,由 worker 调用 skipWaiting()。
const registration = await navigator.serviceWorker.register('/sw.js');registration.addEventListener('updatefound', () => { const worker = registration.installing; worker.addEventListener('statechange', () => { if (worker.state === 'installed' && navigator.serviceWorker.controller) { showUpdateButton(() => worker.postMessage({ type: 'SKIP_WAITING' })); } });});navigator.serviceWorker.addEventListener('controllerchange', () => location.reload());
// sw.jsself.addEventListener('message', (event) => { if (event.data?.type === 'SKIP_WAITING') self.skipWaiting();});navigator.serviceWorker.controller 的检查跳过了首次安装,因为那时没有旧版本可替换。如果页面同时在第二个标签页中打开,那个标签页也会在 controllerchange 时重新加载。
按需检查更新并检测 API
Section titled “按需检查更新并检测 API”一个从不导航的长寿命单页应用,否则只会每 24 小时检查一次更新。下面的代码每小时检查一次,并在 navigator.serviceWorker 缺失时降级为什么都不做;在所有实现该 API 的浏览器中,非安全源(非 localhost 的纯 HTTP)都是这种情况。
if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then((registration) => { setInterval(() => registration.update().catch(() => {}), 60 * 60 * 1000); });} else { // 没有 service worker:页面总是运行已部署的版本,无需检查更新。}离线时 update() 被拒绝是预期行为;在这里吞掉它是正确的,因为下一个周期会重试。
- Service Workers specification: Update algorithm(w3.org)
- Service Workers specification: skipWaiting() method(w3c.github.io)
- The service worker lifecycle(web.dev)
- Service worker 生命周期
- Service worker 注册与 scope
- Service worker 调试
- Workbox
规范
| 规范 | 状态 |
|---|---|
| 无。 | |