更新策略
发布于
完成本指南后,新版本的 Service Worker 会按你选定的方式触达用户,并且有实现该选择的页面代码与 worker 代码:
浏览器的等待默认行为、立即接管,或由用户从「有可用更新」提示中触发的刷新。机制本身(skipWaiting()、
clients.claim()、waiting 状态)见
Service worker 更新流程与 skipWaiting;
本指南讲的是选哪一种以及如何接线。
你需要一个脚本在每次部署之间会变化的已注册 worker。抓取到的脚本与浏览器持有的逐字节不同时,浏览器就视为
有更新;检查发生在导航到 scope 内页面时、push 与 sync 事件时(除非前 24 小时内已检查过),
以及脚本 URL 改变后的 register() 时。多数浏览器在这个检查中忽略 HTTP 缓存头。
保留等待的默认行为
Section titled “保留等待的默认行为”不调用 skipWaiting() 时,新安装的 worker 会等到旧 worker 控制的客户端数为零。默认行为换来的是一致性:
页面始终用它加载时的那个 worker 版本,于是它所依赖的缓存就是作答的缓存。代价是刷新不够:刷新期间新旧页面
短暂重叠,旧 worker 仍控制着一个客户端,所以只有用户关闭或离开所有标签页之后更新才生效。
如果用户会在一天内关闭标签页,并且这段时间内的旧版本可以接受,就选它。
// sw.js:什么都不用加。install 处理函数只做预缓存。self.addEventListener('install', (event) => { event.waitUntil(caches.open('shell-v4').then((cache) => cache.addAll(['/', '/app.js'])));});用 skipWaiting() 立即激活
Section titled “用 skipWaiting() 立即激活”在 install 中调用 self.skipWaiting(),新 worker 在安装完成后立刻激活,哪怕旧 worker 的页面还开着。
当首次安装就应当控制那些没有 worker 时加载的页面,再在 activate 中配上 clients.claim()。
代价是:新 worker 开始为旧版本生成的 HTML 作答,所以一个不向后兼容的缓存或 schema 变更会弄坏这些已打开的页面。
用它来发布应当立即生效、且不具破坏性的修复。
self.addEventListener('install', (event) => { self.skipWaiting(); // 它返回的 Promise 可以忽略 event.waitUntil(caches.open('shell-v4').then((cache) => cache.addAll(['/', '/app.js'])));});
self.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim());});让用户决定何时刷新
Section titled “让用户决定何时刷新”折中方案是安装新 worker、让它等待、告知用户,只在用户接受时才调用 skipWaiting(),随后做一次受控的刷新。
它既避免了无条件 skipWaiting() 的隐性不一致,也避免了默认行为「更新迟迟不生效」的代价。
worker 监听一条消息;页面观察注册对象是否出现等待中的 worker,并在 controllerchange 时只刷新一次。
self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();});// 页面const registration = await navigator.serviceWorker.register('/sw.js');
function offerUpdate(worker) { const banner = document.querySelector('#update-banner'); banner.hidden = false; banner.querySelector('button').onclick = () => worker.postMessage({ type: 'SKIP_WAITING' });}
if (registration.waiting) offerUpdate(registration.waiting);registration.addEventListener('updatefound', () => { const installing = registration.installing; installing.addEventListener('statechange', () => { if (installing.state === 'installed' && navigator.serviceWorker.controller) offerUpdate(installing); });});
let refreshing = false;navigator.serviceWorker.addEventListener('controllerchange', () => { if (refreshing) return; refreshing = true; location.reload();});navigator.serviceWorker.controller 这个检查让首次安装时不弹横幅,因为那时没有旧 worker 可替换。
在长期打开的标签页中检查更新
Section titled “在长期打开的标签页中检查更新”一个开了几天的标签页从不导航,所以从不触发浏览器的检查。registration.update() 抓取脚本并在不同时安装,
上一次抓取距今超过 24 小时时会绕过 HTTP 缓存。每小时调用一次能让自助终端或仪表盘与最新部署的差距保持在
一小时之内,代价是每个标签页每小时多一个小请求。
navigator.serviceWorker.register('/sw.js').then((registration) => { setInterval(() => registration.update(), 60 * 60 * 1000);});按风险匹配策略
Section titled “按风险匹配策略”| 场景 | 策略 | 代价 |
|---|---|---|
| 用户每天都会关闭或离开标签页 | 等待的默认行为 | 最后一个标签页关闭后更新才生效 |
| 标签页会开上几小时甚至几天 | 默认行为加每小时一次 registration.update() |
每个标签页每小时多一个请求 |
| 发布改变了缓存布局或 IndexedDB schema | 默认行为,或用户确认后刷新 | 要设计一条横幅,用户必须接受一次刷新 |
| 应当立即生效的非破坏性修复 | 在 install 中调用 skipWaiting() |
新 worker 为旧版本构建的页面作答 |
部署一个逐字节有变化的 sw.js,在两个标签页里打开应用,在 Service workers 面板里观察所选行为:
默认行为在两个标签页都关闭前一直显示 waiting to activate;skipWaiting() 在下一次刷新时显示新 worker
activated and is running;确认式流程先出横幅,再刷新。
- Service worker 更新流程与 skipWaiting
- Service worker 生命周期:install、activate 与更新
- 离线策略
- The service worker lifecycle(web.dev)
- ServiceWorkerGlobalScope: skipWaiting() method(developer.mozilla.org)
- ServiceWorkerRegistration: update() method(developer.mozilla.org)
← 返回指南总览。