前端 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 路径和操作系统绝对路径怎么转换
-
- 文章 · 前端 | 28分钟前 | 性能优化 · ArrayBuffer TypedArray 前端性能 Web Worker Transferable
- Web Worker 怎样传输大数组减少复制
- 414浏览 收藏
-
- 文章 · 前端 | 30分钟前 | IntersectionObserver 前端性能 rootMargin threshold 滚动监听抖动
- IntersectionObserver 怎样避免滚动监听抖动
- 477浏览 收藏
-
- 文章 · 前端 | 43分钟前 | javascript · JavaScript Fetch ReadableStream 流式响应 TextDecoderStream
- JavaScript ReadableStream 怎样逐块处理响应
- 150浏览 收藏
-
- 文章 · 前端 | 45分钟前 |
- JavaScript fetch 怎样设置超时并可取消
- 100浏览 收藏
-
- 文章 · 前端 | 47分钟前 |
- CSS :has() 按表单校验状态切换父级样式
- 214浏览 收藏
-
- 文章 · 前端 | 53分钟前 |
- Service Worker 怎样只缓存同源静态资源
- 189浏览 收藏
-
- 文章 · 前端 | 16小时前 |
- 前端 WebSocket 退避重连 怎样处理
- 278浏览 收藏
-
- 文章 · 前端 | 16小时前 |
- 前端 Vue watch 深度监听 怎样处理
- 192浏览 收藏
-
- 文章 · 前端 | 16小时前 | React 竞态 Fetch AbortController useEffect 前端异步
- 前端 React useEffect 竞态 怎样处理
- 107浏览 收藏
-
- 文章 · 前端 | 16小时前 |
- 前端 Vite alias 测试失效 怎样处理
- 483浏览 收藏
-
- 文章 · 前端 | 16小时前 |
- 前端 Vite 环境变量前缀 怎样处理
- 161浏览 收藏
-
- 文章 · 前端 | 16小时前 | 前端 · 性能 · javascript · ArrayBuffer postMessage 结构化克隆 Web Worker 前端计算
- 前端 Web Worker 移出计算 怎样处理
- 121浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 615次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 654次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 687次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 649次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 477次使用
-
- 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浏览

