返回
创建于
状态
公开

要实现 Service Worker 中的 Cache Storage 与原生浏览器缓存(Disk Cache/HTTP Cache)“竞速”,核心矛盾在于:Service Worker 本身是拦截器。

默认情况下,当你调用 fetch(event.request) 时,浏览器会先经过 Service Worker 的逻辑,再去尝试它的原生缓存策略。如果你想让两者真正“竞速”,通常是指**“缓存优先,但如果缓存响应太慢,就尽快使用网络(可能命中 Disk Cache)的结果”**。

不过,从底层机制上看,caches.match(Cache Storage)和 fetch(Network/Disk Cache)是并行的。你可以通过 Promise.race 来实现这个逻辑:


竞速模式代码实现

你可以将逻辑重构为如下形式,让缓存和网络同时启动,谁先返回有效结果就用谁:

javascript
1globalSelf.addEventListener('fetch', (event) => {
2  const url = new URL(event.request.url);
3
4  if (url.origin === 'https://help.littleeleven.com' && event.request.method === 'GET') {
5    event.respondWith(
6      (async () => {
7        const cache = await caches.open(HELP_CENTER_CACHE_NAME);
8        
9        // 创建两个 Promise 进行竞速
10        const cachePromise = cache.match(event.request);
11        const networkPromise = fetch(event.request);
12
13        try {
14          // 使用 Promise.race 竞速
15          // 注意:通常我们需要对 Race 结果进行筛选,因为 cache.match 没命中会返回 undefined
16          const response = await Promise.race([
17            cachePromise.then(res => {
18              if (res) return res;
19              // 如果缓存没中,返回一个永远 pending 的 promise,把机会让给网络
20              return new Promise(() => {}); 
21            }),
22            networkPromise.then(networkRes => {
23              if (networkRes.ok) {
24                cache.put(event.request, networkRes.clone());
25              }
26              return networkRes;
27            })
28          ]);
29
30          return response;
31        } catch (error) {
32          // 如果两者都失败(或网络断开且缓存无数据)
33          const cachedResponse = await cachePromise;
34          if (cachedResponse) return cachedResponse;
35
36          return new Response('Network error and no cache', {
37            status: 408,
38            headers: { 'Content-Type': 'text/plain' }
39          });
40        }
41      })()
42    );
43  }
44});

为什么会出现“Cache 慢于 Disk Cache”?

虽然理论上 Cache Storage (IndexedDB 背后) 应该很快,但在某些极端情况下(I/O 繁忙或 SW 唤醒延迟),它确实可能表现不佳。

  1. V8 序列化开销:从 Cache Storage 读取大的二进制文件需要经过 IPC 和序列化。
  2. 浏览器优化:Disk Cache 是内核级别的逻辑,路径更短。

竞速策略建议

如果你非常在意极致性能,建议采用 Stale-While-Revalidate (SWR) 模式:

  • 立即返回缓存(即使它可能比 Disk Cache 慢一点点,但绝大多数情况它依然是最快的)。
  • 后台静默更新:无论缓存是否命中,都在后台发起 fetch 更新 Cache Storage。

关键注意事项

  • 重复请求Promise.race 会导致网络请求总是发出,这会增加用户的流量消耗。
  • AbortController:如果你选择了其中一个结果,理想情况下应该取消另一个正在进行的请求(特别是网络请求),但在 Service Worker 中,简单地忽略 Promise 结果通常比取消请求更易于维护。