前端 Service Worker 的 skipWaiting 和 clientsClaim 怎么安排版本切换
Service Worker 版本切换不要把 skipWaiting() 和 clients.claim() 当成同一个开关:前者让 waiting worker 更早进入激活,后者让已经打开的同源页面被 active worker 接管。默认策略应是让新版本先 waiting;只有旧页面引用的资源、新缓存和新 fetch 路由能够兼容时,才考虑两者一起立即接管。
最稳妥的安排是:先在 install 中准备新缓存,activate 中完成清理和
clients.claim();非破坏更新可以自动切换,可能删除旧资源或改变协议的更新则等待用户确认或下一次导航。
skipWaiting()解决的是“新 worker 还在 waiting”,不负责让页面马上使用它。clients.claim()只能由 active worker 在 activate 阶段接管 scope 内已有页面。- 立即切换前先检查旧页面和新缓存的兼容性,页面侧用
controllerchange防止重复刷新。
先把两个 API 放回 Service Worker 生命周期
一次更新至少要分清四个状态:新脚本下载后进入 install,安装成功后可能停留在 waiting;旧 worker 退出后,新 worker 才进入 activate,页面是否被它控制则由 controller 关系决定。已有页面通常不会因为新 worker 激活就自动重新加载。
| 能力 | 作用位置 | 解决的问题 | 不能替代什么 |
|---|---|---|---|
self.skipWaiting() | Service Worker 内,常放在 install | 让 waiting worker 请求尽快进入激活 | 不会单独接管已有页面 |
self.clients.claim() | active 后,常放在 activate 的 waitUntil | 让 scope 内已有 client 使用当前 worker 控制器 | 不会替你准备缓存或刷新页面 |
controllerchange | 页面侧 | 感知当前页面控制器变更 | 不会判断新旧资源是否兼容 |
skipWaiting() 的返回 Promise 可以不专门等待;而 clients.claim() 应放在 activate 的 event.waitUntil() 中,让激活阶段的异步工作有明确的生命周期。二者一起用时,旧页面可能在一次打开过程中先由旧逻辑加载,再被新 fetch 逻辑接手,这正是版本兼容检查的入口。

先用资源兼容性决定是否立即接管
判断标准不是“新版本已经部署”,而是旧页面继续运行时,能不能安全调用新 worker 的 fetch 逻辑。重点看三件事:旧页面仍会请求哪些 URL,新版本是否删除或改名了这些资源,新 worker 的缓存命名和响应策略是否还能满足旧页面。
例如旧页面仍引用 /assets/app.6.js,而 v7 激活时只保留 app.7.js,并且新的 fetch 路由只从 v7 缓存取文件,那么页面在不刷新的情况下被接管,就可能出现脚本或懒加载模块找不到。此时更适合让 worker 保持 waiting,弹出“发现新版本”,由用户确认后刷新。
反过来,如果资源 URL 使用内容哈希且新旧页面都能访问,或者这次只增加了不影响旧页面的缓存条目,立即接管的风险较低。Workbox 文档也特别提醒:如果懒加载资源使用唯一版本 URL,更新时删除旧预缓存可能让正在运行的旧页面加载失败,不能只看“缓存更新成功”就打开抢占。

把缓存准备、清理和接管写在正确的事件里
下面是一种适合“资源兼容、希望尽快切换”的最小写法。install 只负责准备 v7 缓存;activate 先删除不再允许的缓存,再接管页面。缓存清理失败时让 activate 失败,比激活后悄悄缺资源更容易发现问题。
const CACHE_NAME = "app-shell-v7";
const APP_SHELL = ["/", "/assets/app.7.js", "/assets/app.7.css"];
self.addEventListener("install", (event) => {
// 先把新版本需要的资源完整放入新缓存。
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL)),
);
// 只有确认新旧资源兼容时,才跳过 waiting。
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil((async () => {
const keys = await caches.keys();
// 只删除明确不再允许的旧版本,避免误删运行时缓存。
await Promise.all(
keys.filter((key) => key.startsWith("app-shell-") && key !== CACHE_NAME)
.map((key) => caches.delete(key)),
);
// active 之后再接管当前 scope 内已有页面。
await self.clients.claim();
})());
});
这里的 skipWaiting 不是缓存准备的替代品:如果 cache.addAll() 失败,install 本身不会成功完成。另一个边界是缓存命名,清理范围应与项目自己的前缀一致,不要把第三方库或运行时响应一并删除。
页面侧只刷新一次,并给等待策略留出入口
如果采用立即接管,页面需要监听控制器变化。否则每个 controllerchange 都调用 location.reload(),可能形成重复刷新,尤其是在注册逻辑和更新逻辑同时运行时。
let hasReloadedForController = false;
navigator.serviceWorker.addEventListener("controllerchange", () => {
// 一个页面只为本次控制器切换刷新一次,避免循环刷新。
if (hasReloadedForController) return;
hasReloadedForController = true;
window.location.reload();
});
async function askForUpdate() {
const registration = await navigator.serviceWorker.getRegistration();
// waiting 存在时才提示用户,不把普通注册误报成更新。
if (!registration?.waiting) return;
const accepted = window.confirm("发现新版本,立即刷新吗?");
if (accepted) {
// 只有 worker 自己支持消息协议时才发送这个命令。
registration.waiting.postMessage({ type: "SKIP_WAITING" });
}
}
如果不想让所有更新都自动接管,可以删掉 install 中的 skipWaiting(),保留 waiting worker;页面发现 registration.waiting 后由用户确认,再通过 postMessage 让 worker 调用 self.skipWaiting()。这比把抢占策略硬编码成永远立即更新更适合有长时间打开页面的后台系统。
使用 Workbox 时,workbox-core 的 clientsClaim() 会把调用安排到 activate;而旧的 workbox-core.skipWaiting() wrapper 已不建议继续使用,直接调用 self.skipWaiting() 更清楚。无论是否使用 Workbox,先确定页面资源兼容契约,再选自动还是用户确认。
发布前用一张清单决定切换方式
- 缓存:新版本的 precache 是否在 install 成功后才允许激活,旧缓存删除名单是否明确。
- 页面:旧 HTML 是否可能继续引用不存在的旧资源,懒加载 URL 是否仍可访问。
- 协议:新
fetchhandler 是否改变响应格式、鉴权头或离线回退语义。 - 接管:是否确实需要当前页面立即使用新逻辑,是否有一次性刷新保护。
- 回退:发现不兼容时能否只保持 waiting,或者恢复到可访问的旧资源。
满足兼容性条件时,skipWaiting() 加 clients.claim() 可以缩短用户等待;不满足时,保守地等待下一次导航反而更稳。版本切换的核心不是让 worker 越快变成 active,而是让页面、缓存和 fetch 路由在同一份约束下变化。
常见问题
只调用 skipWaiting,当前页面会立刻被新 worker 控制吗?
不一定。它主要推进 waiting 到 active;已有页面是否被控制还取决于 clients.claim,或者等页面下一次导航/刷新。
clients.claim 能不能放在 install 里?
不应这样安排。clients.claim 需要 active worker 才能稳定接管,通常放在 activate 的 event.waitUntil 中。
每次发布都同时使用两个 API 可以吗?
只有在新旧页面和资源兼容时才适合。若新版本删除旧懒加载文件、改变响应协议或清理旧缓存过早,应让新 worker 保持 waiting。
页面监听 controllerchange 后为什么会刷新两次?
通常是没有设置一次性标志,或注册逻辑重复绑定了监听器。把刷新保护放在模块级状态,并确保注册代码只执行一次。
参考:MDN Service Worker API、MDN skipWaiting()、MDN clients.claim()、Chrome for Developers Workbox Core。
Go 多个生产者场景怎么安全决定谁负责关闭 channel
- 上一篇
- Go 多个生产者场景怎么安全决定谁负责关闭 channel
- 下一篇
- Go io/fs 路径和操作系统绝对路径怎么转换
-
- 文章 · 前端 | 1小时前 |
- 前端 createObjectURL 预览文件后什么时候 revoke
- 422浏览 收藏
-
- 文章 · 前端 | 2小时前 |
- 前端 Blob.slice 上传分片时怎么计算最后一片长度
- 432浏览 收藏
-
- 文章 · 前端 | 3小时前 |
- 前端 IndexedDB 事务自动提交前为什么不能异步等待
- 398浏览 收藏
-
- 文章 · 前端 | 5小时前 | 前端 · 浏览器存储 · IndexedDB · 多标签页 IndexedDB versionchange onblocked
- 前端 IndexedDB 版本升级被阻塞时怎么提示旧页面关闭
- 383浏览 收藏
-
- 文章 · 前端 | 6小时前 | 前端 · javascript · 流式读取 · Fetch AbortController ReadableStream
- 前端 ReadableStream 读取中断后怎么取消底层请求
- 306浏览 收藏
-
- 文章 · 前端 | 7小时前 | 前端 · 性能优化 · javascript · Fetch API · Fetch AbortController ReadableStream TextDecoderStream 大文件分块
- 前端 fetch 大文件时怎么用 ReadableStream 逐块处理
- 483浏览 收藏
-
- 文章 · 前端 | 8小时前 | javascript · cors · 网络请求 · Fetch API · 前端 cors Fetch 网络错误 response.ok
- 前端 fetch 网络断开时怎么区分 CORS 和真正连接失败
- 424浏览 收藏
-
- 文章 · 前端 | 9小时前 |
- 前端 fetch 收到 404 为什么不会自动进入 catch
- 299浏览 收藏
-
- 文章 · 前端 | 11小时前 | 前端 · 文件上传 · javascript · Blob · fetch · 大文件上传 断点续传 分片上传 Blob.slice 可恢复上传
- 前端上传大文件怎么用 Blob slice 实现可恢复分片
- 321浏览 收藏
-
- 文章 · 前端 | 12小时前 |
- Web Worker 传输 ArrayBuffer 后主线程为什么不能再读取
- 364浏览 收藏
-
- 文章 · 前端 | 13小时前 |
- CSS container query 和 media query 应该按什么条件选择
- 173浏览 收藏
-
- 文章 · 前端 | 15小时前 | pwa · Service Worker · 前端缓存 · 缓存更新 Service Worker skipWaiting clients.claim Cache Storage
- Service Worker 更新后旧缓存为什么还在使用
- 251浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 19次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 177次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 111次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 38次使用
-
- Generrated
- Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
- 18次使用
-
- Adobe Express怎么安装到电脑?网页版、PWA入口与更新检查
- 2026-08-15 324浏览
-
- Go语言对前端领域的入侵WebAssembly运行原理
- 2022-12-31 130浏览
-
- go+gin 静态资源路由与后端api路由冲突如何解决?
- 2023-02-24 414浏览
-
- 分片上传文件,后端接收怎么生成了一个文件名为blob的文件?
- 2023-01-09 163浏览
-
- 除了cookie之外,还能用什么方法做验证码功能?
- 2023-01-22 137浏览

