登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

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

实验目录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删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>