Service Worker 怎样区分缓存优先和网络优先
来源:17golang原创
时间:2026-11-08 05:07:08 320浏览 收藏
缓存优先是先查询 Cache Storage,命中就返回,未命中才请求网络;网络优先是先发起网络请求,按应用约定在网络不可用等条件下回退到缓存。两者区别在响应来源的优先级,不能只凭“使用了 Service Worker”判断页面会不会更新,也不能把网络优先理解为永远获得最新服务端数据。
Web API 文档入口:https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers
准备实验:先确定资源能否使用旧内容
本实验使用两个公开、与用户身份无关的文件:banner.v1.txt 代表版本固定的展示素材,catalog.json 代表更关注更新的公开目录。前者采用缓存优先,后者采用网络优先。文件内容由你自行准备,目录文件可先写合法 JSON,例如包含一个版本字段;不要将账号信息放进实验资源。
| 策略 | 命中缓存且在线 | 断网且有缓存 | 主要取舍 |
|---|---|---|---|
| 缓存优先 | 直接用缓存,不主动请求更新 | 返回缓存 | 稳定快速,需另设更新机制 |
| 网络优先 | 先请求网络,成功响应可更新缓存 | 网络失败后返回缓存 | 更新机会更多,可能等待慢网络 |
表格描述的是本文代码约定。其他实现可能对超时、HTTP 错误、缓存过期作不同处理,因此讨论策略时必须同时写出回退条件。
初始化独立目录并注册 Service Worker
在测试站点根目录下准备 /sw-lab/,放入 index.html、sw.js 以及两个实验文件。用 HTTPS 或支持本地开发的 localhost 环境访问,不能直接双击本地 HTML 文件。Worker 脚本应以正确的 JavaScript 内容类型返回。
把以下代码放进测试页面的脚本中。注册 scope 限定在实验目录,不影响站点其他页面。
// 页面脚本:注册脚本位置同时决定默认可用作用域。
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw-lab/sw.js", {
scope: "/sw-lab/"
}).then(() => {
console.log("注册请求完成;首次安装后重新访问页面再观察控制状态");
}).catch((error) => {
console.error("注册失败", error);
});
}
注册完成不等于当前页面已受控。首次安装激活后重新访问页面,并在页面脚本中查看 navigator.serviceWorker.controller 是否非空。本文不使用强制跳过等待或立即接管,避免把更新生命周期与策略实验混在一起。
为两个公开文件编写不同缓存策略
将下面代码保存为 sw.js。它只处理两个明确路径,不处理 POST、不处理导航页面,也不尝试把全部接口都缓存。读取缓存失败时继续尝试网络;写缓存失败时仍保留可返回的网络响应。
const CACHE_NAME = "sw-strategy-lab-v1";
async function readCached(request) {
try {
const cache = await caches.open(CACHE_NAME);
return await cache.match(request);
} catch {
// 存储不可用不应阻止在线请求。
return undefined;
}
}
async function fetchAndStore(request) {
// 实验文件是公开资源;绕过 HTTP 缓存以便观察服务端更新。
const response = await fetch(request, {
cache: "no-store",
credentials: "omit"
});
if (response.ok && response.type === "basic" && !response.redirected) {
try {
const cache = await caches.open(CACHE_NAME);
// 响应体需要分别用于缓存和页面,所以先 clone。
await cache.put(request, response.clone());
} catch {
// 配额或写入失败仍返回网络响应。
}
}
return response;
}
function unavailable() {
// 明确返回不可用,不能用空白的成功响应掩盖缺失数据。
return new Response("实验资源当前不可用", {
status: 503,
headers: { "Content-Type": "text/plain; charset=utf-8" }
});
}
async function cacheFirst(request) {
const cached = await readCached(request);
if (cached) return cached;
try {
return await fetchAndStore(request);
} catch {
return unavailable();
}
}
async function networkFirst(request) {
try {
// 本例 HTTP 404/500 原样返回;只有 fetch 拒绝才回退。
return await fetchAndStore(request);
} catch {
return (await readCached(request)) || unavailable();
}
}
self.addEventListener("fetch", (event) => {
const request = event.request;
const url = new URL(request.url);
// 只接管两个公开 GET 文件,不接管页面、登录或其他接口。
if (request.method !== "GET" || url.origin !== self.location.origin ||
url.search !== "") return;
if (url.pathname === "/sw-lab/banner.v1.txt") {
event.respondWith(cacheFirst(request));
} else if (url.pathname === "/sw-lab/catalog.json") {
event.respondWith(networkFirst(request));
}
});
event.respondWith() 在 fetch 事件监听器中同步调用,传入的 Promise 再异步决定响应来源。response.clone() 为缓存保留独立的响应体读取分支,避免页面与缓存共同消费同一份流。
代码使用 cache: "no-store" 请求网络,方便实验时区分 HTTP 缓存与 Cache Storage。它不会禁止这里显式执行的 cache.put()。Cache Storage 是应用管理的存储,不应期待它自动按照 HTTP 缓存头替你过期;真实项目应明确可缓存白名单和失效规则。

在线更新与离线回退分别观察什么
受控页面可在同一测试会话中请求两个资源,用正文内容中的版本标记识别变化。首次必须在线成功获取资源,才能有可用缓存;如果文件从未获取成功,断网时出现 503 是代码的预期回退。
// 在已受控的实验页面执行,仅请求本例公开文件。
async function readLabFiles() {
for (const path of ["/sw-lab/banner.v1.txt", "/sw-lab/catalog.json"]) {
const response = await fetch(path);
console.log(path, response.status, await response.text());
}
}
readLabFiles();
先在线请求一次,再修改服务端文件内容并保持 URL 不变。再次请求时,缓存优先的 banner 应继续返回原缓存;网络优先的目录会尝试获取修改后的内容。然后在自己的测试环境模拟断网:已有缓存的两个资源都可回退,但网络优先会先等待请求失败。
这些是读者可以观察的预期条件,本文没有在本机运行实验,也没有提供伪造截图或耗时数据。若结果不符,先区分页面是否受控、实际命中路径、当前 Worker 是否仍为旧版本,以及缓存是否已成功写入。

扩展实验:HTTP 错误和慢网络怎么处理
fetch 得到 404 或 500 时通常仍会兑现为 Response,不会因为状态码错误自动进入 catch。本文网络优先保留 HTTP 错误响应,也不将它们覆盖进缓存。若产品希望对部分 5xx 回退,可明确列出允许的状态;401、403 等身份或权限变化不应被旧内容无条件遮盖。
慢网络也不一定很快触发失败。本例没有增加网络超时,因此不能声称“网络优先几秒后必定离线回退”。生产环境若加超时,需要定义超时后的请求是否取消、晚到响应是否仍更新缓存,并管理相应任务生命周期。
缓存优先若需要更新,可采用内容哈希文件名、版本化 URL 或受控的缓存淘汰机制。若每次命中缓存后都在后台刷新,那更接近 stale-while-revalidate,应作为另一种策略解释,不能把它和本文纯缓存优先混称。
清理缓存并确定生产采用边界
实验结束后,只删除本文命名的缓存并注销对应脚本的注册,不批量清空同源其他业务存储。以下为测试页面清理示例,关闭相关受控标签页后重新访问,避免把现有页面的 controller 状态当成注册仍未注销的证据。
async function clearLab() {
// 精确删除实验缓存,保留同源其他业务缓存。
await caches.delete("sw-strategy-lab-v1");
const registrations = await navigator.serviceWorker.getRegistrations();
for (const registration of registrations) {
// 根据本例脚本地址识别注册,避免注销其他 Worker。
const worker = registration.active || registration.waiting || registration.installing;
if (worker && new URL(worker.scriptURL).pathname === "/sw-lab/sw.js") {
await registration.unregister();
}
}
}
clearLab().catch(console.error);
网络优先能保证绝对新鲜吗? 不能,还取决于 HTTP/CDN 缓存、服务端版本和请求是否成功。策略只决定本地处理优先级,不提供跨系统一致性保证。
缓存优先一定能离线打开整个页面吗? 不能。本文只缓存两个文件,页面 HTML、其他资源和业务数据仍可能缺失。完整离线体验需要单独设计导航回退和资源集合。
注销 Worker 会自动删除缓存吗? 不会,应分别管理注册与 Cache Storage。存储也可能被浏览器清理,不能把它当作永久保存。
选择策略时先判断资源能容忍多旧的内容,再明确命中路径、回退条件和淘汰方法。稳定的版本资源适合缓存优先,关注更新的公开内容可以考虑网络优先;涉及身份、权限或必须实时的数据,应按业务一致性要求单独设计。
接口参考:https://developer.mozilla.org/en-US/docs/Web/API/Cache;https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/fetch_event。
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
110 收藏
-
文章 · 前端 | 39分钟前 | javascript · css · View Transition API view-transition-name Element.startViewTransition409 收藏
-
303 收藏
-
293 收藏
-
199 收藏
-
127 收藏
-
406 收藏
-
265 收藏
-
207 收藏
-
305 收藏
-
332 收藏
-
313 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习