当前位置:首页 > 文章列表 > 文章 > 前端 > Service Worker 怎样区分缓存优先和网络优先

Service Worker 怎样区分缓存优先和网络优先

来源:17golang原创 2026-11-08 05:07:08 0浏览 收藏

缓存优先是先查询 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 是否仍为旧版本,以及缓存是否已成功写入。

实验目录Worker作用域公开资源缓存命名和资源版本的管理关系
静态说明图:实验目录限定 Worker 作用域,缓存名称与资源版本各有职责;修改 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。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
View Transition API 怎样只过渡局部元素View Transition API 怎样只过渡局部元素
上一篇
View Transition API 怎样只过渡局部元素
农产品加工企业怎样准备批次追溯记录
下一篇
农产品加工企业怎样准备批次追溯记录
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    937次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    940次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1049次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1026次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    850次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码