缓存策略总览

在离线优先的 REST API 架构中,没有“万能”的缓存策略。不同的接口对数据时效性、网络依赖和用户体验的要求各不相同。Cache‑First、Network‑First、Stale‑While‑Revalidate、Cache‑Only 和 Network‑Only 五种策略构成了 Service Worker 缓存的核心决策工具箱。本章将系统梳理每种策略的原理、实现流程、适用场景与常见误区,为后续章节的深度实践打下基础。

策略选择的三个决策维度

在具体了解每种策略之前,需要明确三个关键维度,它们直接决定了策略的选型:

  • 网络状态:在线、弱网、离线三种环境下,策略的行为差异。
  • 数据时效性:接口返回的数据是否允许“秒级延迟”更新,还是必须实时最新。
  • 离线需求:该接口是否必须在离线状态下仍能正常返回内容。

五种策略快速对比

策略 核心原则 网络依赖 离线可用 数据时效性 典型场景
Cache-First 缓存命中即返回,否则请求网络 弱 是 中(缓存TTL内可能过时) 静态资源、用户头像、品牌配置
Network-First 先请求网络,失败后回退缓存 强 是(降级) 高 订单详情、账户余额、实时数据
Stale-While-Revalidate 立即返回缓存,后台发起网络更新 弱 是 最终一致 文章列表、商品推荐、通知
Cache-Only 仅从缓存读取,绝不请求网络 无 是 固定(仅预缓存内容) 使用条款、帮助页面、静态配置
Network-Only 仅从网络获取,绝不读取缓存 必选 否 实时 支付接口、验证码、SSE 推送
设计建议:在实际项目中,一个应用通常会同时使用 3~4 种策略。例如:静态资源用 Cache-First,核心数据用 Network-First,列表页用 Stale-While-Revalidate,关键静态文档用 Cache-Only,支付接口用 Network-Only。
五种缓存策略决策与流程总览 五种缓存策略决策与流程总览 客户端请求 Cache-First Network-First Stale-While-Revalidate Cache-Only Network-Only 缓存命中 → 直接返回 未命中 → 请求网络并缓存 请求网络 → 成功则返回 失败 → 回退缓存 立即返回缓存(可能旧) 后台发起网络请求更新缓存 仅从缓存读取 未命中 → 返回错误 仅从网络获取 离线 → 直接失败 HTTP 响应 策略选择取决于数据时效性要求与离线可用性需求,实际应用中常组合使用

Cache-First(缓存优先)

Cache-First 是所有策略中最“激进”的缓存策略。只要有缓存命中,就直接返回缓存内容,完全跳过网络请求。只有当缓存不存在时,才发出网络请求,并将响应写入缓存。

实现流程

  1. 拦截请求,查询缓存。
  2. 缓存命中 → 直接返回缓存响应(结束)。
  3. 缓存未命中 → 发起网络请求。
  4. 将网络响应克隆一份写入缓存。
  5. 返回网络响应给页面。

Service Worker 实现

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      // 缓存命中即返回
      if (cached) return cached;
      // 未命中则请求网络
      return fetch(event.request).then((response) => {
        return caches.open('api-cache').then((cache) => {
          cache.put(event.request, response.clone());
          return response;
        });
      });
    })
  );
});
适用场景:用户头像、品牌 Logo、静态配置 JSON、版本固定的帮助文档。这些资源变更频率极低,缓存命中率可达 95% 以上。

Network-First(网络优先)

Network-First 优先保证数据的新鲜度。每次请求都先尝试网络,只有网络失败(超时、离线、服务器错误)时才回退到缓存。这是在线优先场景下的“安全降级”策略。

实现流程

  1. 发起网络请求。
  2. 网络成功 → 将响应克隆写入缓存,返回响应。
  3. 网络失败 → 查询缓存。
  4. 缓存命中 → 返回缓存响应(降级数据)。
  5. 缓存未命中 → 返回错误响应。

Service Worker 实现

self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .then((response) => {
        return caches.open('api-cache').then((cache) => {
          cache.put(event.request, response.clone());
          return response;
        });
      })
      .catch(() => {
        return caches.match(event.request).then((cached) => {
          if (cached) return cached;
          return new Response('Network unavailable', { status: 503 });
        });
      })
  );
});
常见陷阱:Network-First 在弱网环境下用户体验较差,因为用户需要等待网络超时(通常 5~15 秒)才会回退缓存。建议为 fetch 设置自定义超时时间,例如 3 秒未响应就降级。

Stale-While-Revalidate(过期重新验证)

Stale-While-Revalidate 是“最终一致性”的经典实现。它立即返回缓存内容(即使已经过期),同时在后台发起网络请求更新缓存。用户永远不需要等待网络,但下次请求就能拿到更新后的数据。

实现流程

  1. 查询缓存,立即返回缓存内容(可能过期)。
  2. 在后台发起网络请求。
  3. 网络成功 → 更新缓存(不阻塞当前响应)。
  4. 网络失败 → 保留旧缓存,下次继续使用。

Service Worker 实现

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      // 后台更新
      const fetchPromise = fetch(event.request)
        .then((response) => {
          return caches.open('api-cache').then((cache) => {
            cache.put(event.request, response.clone());
            return response;
          });
        })
        .catch(() => cached);
      // 立即返回缓存,如果不存在则等待网络
      return cached || fetchPromise;
    })
  );
});
最佳实践:Stale-While-Revalidate 非常适合 REST API 中的列表接口(如 /api/articles、/api/products)。用户感知到的速度极快,同时数据在几秒内自动更新,体验接近实时。

Cache-Only(仅缓存)

Cache-Only 策略完全不依赖网络,所有数据必须在安装阶段预缓存到 Cache Storage 中。如果缓存中不存在对应资源,请求直接失败。这是最可靠的离线策略,但灵活性最低。

实现流程

  1. 拦截