缓存策略总览
在离线优先的 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(缓存优先)
Cache-First 是所有策略中最“激进”的缓存策略。只要有缓存命中,就直接返回缓存内容,完全跳过网络请求。只有当缓存不存在时,才发出网络请求,并将响应写入缓存。
实现流程
- 拦截请求,查询缓存。
- 缓存命中 → 直接返回缓存响应(结束)。
- 缓存未命中 → 发起网络请求。
- 将网络响应克隆一份写入缓存。
- 返回网络响应给页面。
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 优先保证数据的新鲜度。每次请求都先尝试网络,只有网络失败(超时、离线、服务器错误)时才回退到缓存。这是在线优先场景下的“安全降级”策略。
实现流程
- 发起网络请求。
- 网络成功 → 将响应克隆写入缓存,返回响应。
- 网络失败 → 查询缓存。
- 缓存命中 → 返回缓存响应(降级数据)。
- 缓存未命中 → 返回错误响应。
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 是“最终一致性”的经典实现。它立即返回缓存内容(即使已经过期),同时在后台发起网络请求更新缓存。用户永远不需要等待网络,但下次请求就能拿到更新后的数据。
实现流程
- 查询缓存,立即返回缓存内容(可能过期)。
- 在后台发起网络请求。
- 网络成功 → 更新缓存(不阻塞当前响应)。
- 网络失败 → 保留旧缓存,下次继续使用。
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 中。如果缓存中不存在对应资源,请求直接失败。这是最可靠的离线策略,但灵活性最低。
实现流程
- 拦截