跳转到内容

Service Worker 中的概念

安全移除 service worker(kill switch)

发布于

自 2018-04 起广泛可用W3C 草案

一个用缓存响应请求的 service worker,在服务端修好之后仍会继续那样响应,因为决定受控页面拿到什么的是这个 worker,而不是服务端。修复手段是移除注册,而执行这一步的调用是 ServiceWorkerRegistration.unregister()。

移除其实是两件容易混为一谈的事:取消注册,以及释放旧 worker 仍在控制的那些页面。规范把两者明确分开,而后者并不由页面说了算。

unregister() 不接受参数,返回一个 promise。找不到注册时该 promise 以 false 兑现,否则以 true 兑现,「irrespective of whether unregistration happened or not (it may not unregister if someone else just called ServiceWorkerContainer.register() with the same scope.)」;service worker 会在被注销之前完成正在进行的操作(ServiceWorkerRegistration: unregister() method,developer.mozilla.org)。MDN 把该方法记为 Baseline 广泛可用,自 2018 年 4 月起在各浏览器中可用,并且在部分或全部支持它的浏览器中仅在安全上下文可用。

规范的 Unregister 算法解释了为什么兑现值几乎说明不了结果。它按任务的 storage key 与 scope url 取出注册;若为 null,就以 false 兑现任务 promise 并结束。否则移除该键与 scope 对应的 registration map 条目,以 true 兑现任务 promise,再调用 Try Clear Registration(Service Workers,w3c.github.io)。因此 true 报告的是映射条目已被移除,而不是 worker 已经停止。

已打开的页面为何仍留着旧 worker

Section titled “已打开的页面为何仍留着旧 worker”

清理是延后的。规范指出,当 Try Clear Registration 在那一步没有触发 Clear Registration 时,「Clear Registration is tried again when the last client using the registration is unloaded or the extend lifetime promises for the registration’s service workers settle」。方法自身那条注释说得更直接:承载受控客户端活动 service worker 的那份注册「is effective until all the service worker clients (including itself) using this service worker registration unload. That is, the unregister() method only affects subsequent navigations.」

这正是让 kill switch 看起来失效的那个行为。执行了调用的那个标签页仍挂着旧的控制者,所以它接下来的 fetch 依然走进旧的处理器。结束这段注册效力的,是一次重新加载,或者关闭该标签页。

Unregister 算法作用于 registration map 与该注册的那些 worker。缓存响应要通过另一个接口才能触及:CacheStorage.keys() 以一个字符串数组兑现,按创建顺序列出该 CacheStorage 跟踪的每一个具名 Cache 对象(CacheStorage: keys() method,developer.mozilla.org),而 CacheStorage.delete() 找到与 cacheName 匹配的 Cache 并删除它、以 true 兑现,找不到同名缓存时以 false 兑现(CacheStorage: delete() method,developer.mozilla.org)。清理可以直接删除一份明确的已知缓存名称列表;只有必须先发现这些名称时才需要调用 keys()。

keys() 返回的是这个 CacheStorage 对象相关名称到缓存映射中的每一个缓存名称,而不是一份归属于被移除 worker 的列表。该删的是被移除应用自己拥有的那些缓存,这也是下面的示例按应用自己的名称前缀去匹配、而不是照搬整份列表的原因。

第一个示例在仍能正常加载的页面里移除注册;第二个覆盖的是浏览器只肯运行那个坏掉的 worker 的情况。

移除当前客户端 storage key 下的每份注册

Section titled “移除当前客户端 storage key 下的每份注册”

navigator.serviceWorker.getRegistrations() 以数组形式取回与某个 ServiceWorkerContainer 关联的全部 ServiceWorkerRegistration 对象(ServiceWorkerContainer: getRegistrations() method,developer.mozilla.org),这正是一次调用就能覆盖到当前页面并不知情的某个 scope 上的 worker 的原因。

// 本应用创建的每个缓存都带这个名称前缀,所以这里只允许删掉
// 带该前缀的名字。
const OWNED_CACHE_PREFIX = 'app-';
async function removeAllWorkers() {
if (!('serviceWorker' in navigator)) {
// 该上下文没有 worker API:也就没有已注册的东西需要移除。
return { supported: false, removed: 0 };
}
const registrations = await navigator.serviceWorker.getRegistrations();
const results = await Promise.all(registrations.map((reg) => reg.unregister()));
const names = await caches.keys();
const owned = names.filter((name) => name.startsWith(OWNED_CACHE_PREFIX));
await Promise.all(owned.map((name) => caches.delete(name)));
return { supported: true, removed: results.filter(Boolean).length, cachesDeleted: owned.length };
}

不支持的分支返回一份结构化结果,因为当前上下文没有暴露 Service Worker API。已被旧 worker 控制的页面会一直留着它直到卸载,所以调用方仍需让这些页面重新加载。把清理限制在本应用范围内,靠的就是那个前缀过滤:换成被移除 worker 实际用过的命名方案;若该方案没有共同前缀,就改为维护一份明确的名称列表。

部署一个会把自己注销掉的 worker

Section titled “部署一个会把自己注销掉的 worker”

当已部署的 worker 提供的正是上一个示例所在页面的旧副本时,替换者就只能以 service worker 的身份到场。self.registration 返回指向该 worker 自身 ServiceWorkerRegistration 对象的引用(ServiceWorkerGlobalScope: registration property,developer.mozilla.org),而 skipWaiting() 让新 worker 不必等旧 worker 的客户端关闭就能激活(ServiceWorkerGlobalScope: skipWaiting() method,developer.mozilla.org)。

// sw.js,用来顶替正在被移除的那个 worker
const OWNED_CACHE_PREFIX = 'app-';
self.addEventListener('install', () => {
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil((async () => {
const names = await caches.keys();
await Promise.all(
names
.filter((name) => name.startsWith(OWNED_CACHE_PREFIX))
.map((name) => caches.delete(name)),
);
await self.registration.unregister();
})());
});

这个脚本不注册 fetch 处理器,所以一旦它成为活动 worker,它的客户端在下一次导航时就走网络。注册本身由那次 unregister() 调用移除,而该注册名下 worker 的清理,按上面引用的规范注释,发生在最后一个使用它的客户端卸载之时。

规范

规范状态
Service WorkerW3C 草案
  • 图例
  • 支持
  • 部分支持
  • 需开启标志
  • 不支持
  • 未知
浏览器 / 平台支持版本置信度来源备注
Chrome (Android)支持40中来源—
Chrome (Desktop)支持40中来源—
Edge (Desktop)支持17中来源—
Safari (iOS)支持11.3中来源1
Safari (macOS)支持11.1中来源—
Firefox (Desktop)支持44中来源—
Samsung Internet支持4.0中来源—
  1. 长期不使用后存储可能被逐出。

源数据: /compatibility/service-worker.json · 全球使用占比: 93 % (StatCounter 2026-05)

来源: 规范 · MDN · 最近核验 2026-06-24 · 置信度: 中 (由来源计算)