Service Worker 缓存策略

到目前为止, Cache 接口。 要有效使用 Service Worker,需要采用一种或多种缓存策略, 这需要您对 Cache 接口有一定的了解。

缓存策略是 Service Worker 的 fetch 事件与 Cache 接口之间的交互。 缓存策略的编写方式取决于: 例如,最好采用与处理文档不同的方式来处理对静态资源的请求, 这会影响缓存策略的组成。

在介绍策略本身之前 我们先来介绍一下 Cache 接口的不同之处。 以及它提供的用于管理 Service Worker 缓存的一些方法的简要介绍。

Cache 接口与 HTTP 缓存

如果您之前没有使用过 Cache 接口, 你可能很想把它想象成 或至少与 HTTP 缓存相关事实并非如此。

  • Cache 接口是一种完全独立于 HTTP 缓存的缓存机制。
  • 任意内容 Cache-Control 您用来影响 HTTP 缓存的配置不会影响存储在 Cache 接口中的资产。

不妨将浏览器缓存视为分层。 HTTP 缓存是一种低级缓存,由键值对驱动,其中指令以 HTTP 标头表示。

相比之下,Cache 接口是由 JavaScript API 驱动的高级缓存。 与使用相对简单的 HTTP 键值对相比,这可提供更大的灵活性, 是实现缓存策略的一半。 关于 Service Worker 缓存的一些重要 API 方法包括:

这些只是其中的几个。还有其他一些实用的方法 但这些是您在本指南的后面部分会用到的基本功能。

不起眼的 fetch 活动

缓存策略的另一半是 Service Worker fetch 事件。 到目前为止,在本文档中,您已经听说过“拦截网络请求”, 在 Service Worker 内的 fetch 事件中,发生以下情况:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(cacheName));
});

self.addEventListener('fetch', async (event) => {
  // Is this a request for an image?
  if (event.request.destination === 'image') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Respond with the image from the cache or from the network
      return cache.match(event.request).then((cachedResponse) => {
        return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {
          // Add the network response to the cache for future visits.
          // Note: we need to make a copy of the response to save it in
          // the cache and use the original as the request response.
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

这是一个玩具示例 你可以亲自体验一下 可让您大致了解 Service Worker 的功能。 上述代码会执行以下操作:

  1. 检查请求的 destination 属性,确认这是否为图片请求。
  2. 如果图片位于 Service Worker 缓存中,请从这里提供图片。 如果没有,则从网络中提取图片。 将响应存储在缓存中,并返回网络响应。
  3. 所有其他请求均通过 Service Worker 传递,不与缓存交互。

提取操作的 event 对象包含 request 个媒体资源 下面提供了一些有助于您识别每个请求类型的实用信息:

  • url, 这是当前由 fetch 事件处理的网络请求的网址。
  • method, 即请求方法(例如,GETPOST)。
  • mode, 用于说明请求的模式 值 'navigate' 通常用于区分对 HTML 文档的请求与其他请求。
  • destination, ,用于说明所请求的内容类型,并避免使用所请求资源的文件扩展名。

同样,异步是游戏的名称。 您可能还记得,install 事件提供了 event.waitUntil 方法,该方法接受 promise 并等待其解析,然后再继续激活。 fetch 事件会提供类似的 event.respondWith 方法 可用于返回异步结果 fetch 项请求 或者由 Cache 接口的 match 方法

缓存策略

现在,您对 Cache 实例和 fetch 事件处理脚本有了一定的了解, 现在,您可以深入了解一些 Service Worker 缓存策略了。 虽然有几乎无穷无尽的可能 本指南将沿用 Workbox 随附的策略, 因此您可以了解 Workbox 的内部结构。

仅缓存

显示从页面到 Service Worker 和缓存的流。

我们先从一个简单的缓存策略开始,我们称之为“仅缓存”。 就是这样:当 Service Worker 控制页面时, 匹配的请求只会进入缓存 这意味着,所有缓存的资源都需要先预缓存,然后才能使用该模式。 在更新 Service Worker 之前,这些资源永远不会在缓存中更新。

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

// Assets to precache
const precachedAssets = [
  '/possum1.jpg',
  '/possum2.jpg',
  '/possum3.jpg',
  '/possum4.jpg'
];

self.addEventListener('install', (event) => {
  // Precache assets on install
  event.waitUntil(caches.open(cacheName).then((cache) => {
    return cache.addAll(precachedAssets);
  }));
});

self.addEventListener('fetch', (event) => {
  // Is this one of our precached assets?
  const url = new URL(event.request.url);
  const isPrecachedRequest = precachedAssets.includes(url.pathname);

  if (isPrecachedRequest) {
    // Grab the precached asset from the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request.url);
    }));
  } else {
    // Go to the network
    return;
  }
});

如上所示,一系列资源已在安装时预缓存。 当 Service Worker 处理提取时, 我们将检查由 fetch 事件处理的请求网址是否在预缓存资源数组中。 如果确实如此,我们从缓存中抓取资源并跳过网络。 其他请求会传递到网络 且只能选择广告联盟 要查看此策略的实际应用 查看此演示,同时打开控制台。

仅限网络

显示从页面到 Service Worker 和网络的流。

与“仅缓存”相反设为“仅广告联盟” 其中,请求通过 Service Worker 传递到网络,而无需与 Service Worker 缓存进行任何交互。 这是确保内容新鲜度(考虑标记)的好策略, 但需要权衡的是,当用户离线时,此方法将始终无效。

确保请求传递到广告联盟只是意味着您无需针对匹配的请求调用 event.respondWith。 如果您想使用明确的政策 您可以在 fetch 事件回调中为要传递到广告网络的请求设置空的 return;。 在“仅缓存”模式下针对未预缓存的请求的策略演示。

先缓存,然后回退到网络

显示从页面到 Service Worker、到缓存再到网络的流(如果不在缓存中)。

在此策略中,需要涉及更多工作。 对于匹配请求,流程如下:

  1. 请求命中缓存。如果资源在缓存中,请从缓存中提供。
  2. 如果请求不在缓存中,请转到网络。
  3. 在网络请求完成后,将其添加到缓存中, 然后从网络返回响应。

这是此策略的一个示例 现场演示

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a request for an image
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the cache first
      return cache.match(event.request.url).then((cachedResponse) => {
        // Return a cached response if we have one
        if (cachedResponse) {
          return cachedResponse;
        }

        // Otherwise, hit the network
        return fetch(event.request).then((fetchedResponse) => {
          // Add the network response to the cache for later visits
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

虽然此示例仅涵盖图片 这是一个绝佳的策略,适用于所有静态资源(例如 CSS、JavaScript、图片和字体), 尤其是采用哈希技术的算法 它通过绕过 HTTP 缓存可能启动的任何服务器的内容新鲜度检查,来提高不可变资源的速度。 更重要的是,所有缓存的资源将可离线使用。

网络优先,回退到缓存

显示从页面到 Service Worker、到网络,再到缓存(如果网络不可用)的流程。

如果您要切换“缓存优先,网络第二个”头顶 最终会出现“网络优先,缓存第二个”的情形大概就是这样:

  1. 您首先要访问网络以获取请求,然后将响应放入缓存中。
  2. 如果您稍后处于离线状态, 则您会在缓存中回退到该响应的最新版本。

此策略非常适合存在以下情况的 HTML 或 API 请求: 在线时,您需要的是资源的最新版本 但您想让其离线访问的是最新可用版本。 以下是应用于 HTML 请求时可能的内容:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a navigation request
  if (event.request.mode === 'navigate') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the network first
      return fetch(event.request.url).then((fetchedResponse) => {
        cache.put(event.request, fetchedResponse.clone());

        return fetchedResponse;
      }).catch(() => {
        // If the network is unavailable, get
        return cache.match(event.request.url);
      });
    }));
  } else {
    return;
  }
});

您可以通过演示试用此功能。 首先,转到相应网页。在将 HTML 响应放入缓存之前,您可能需要重新加载。 然后在开发者工具中 模拟离线连接, 然后再次重新加载 系统将立即从缓存中提供最新的可用版本。

在离线功能非常重要的情况下 但您需要在该功能与访问最新版本的标记或 API 数据之间取得平衡, “网络优先,缓存第二个” 是实现这一目标的可靠策略。

过时重新验证

显示从页面到 Service Worker,再到缓存,再从网络到缓存的流。

在到目前为止我们介绍过的策略中,是最复杂的。 从某些方面来看,它与后两种策略类似, 但该过程会优先考虑对资源的访问速度 同时还要在后台保持最新状态该策略大致如下所示:

  1. 第一次请求资源时,从网络中提取资源 将其放在缓存中,并返回网络响应。
  2. 对于后续请求,先从缓存中提供资源,然后再“在后台”提供资源, 从网络重新请求该资产并更新资产的缓存条目。
  3. 在此日期之后再提交请求 您将收到从网络提取的最后一个版本(在上一步中放入缓存)。

这个策略非常适合需要更新的一些重要信息, 但并非至关重要。 想像一下社交媒体网站的头像。 这些信息会在用户进行适当操作时进行更新 但并非每个请求都绝对要求使用最新版本。

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request).then((cachedResponse) => {
        const fetchedResponse = fetch(event.request).then((networkResponse) => {
          cache.put(event.request, networkResponse.clone());

          return networkResponse;
        });

        return cachedResponse || fetchedResponse;
      });
    }));
  } else {
    return;
  }
});

您可以在 这是另一个现场演示 尤其是当您注意浏览器开发者工具中的“Network”(网络)标签页时, 及其 CacheStorage 查看器(如果您的浏览器的开发者工具包含此类工具)。

继续了解 Workbox!

本文是对 Service Worker API 的总结, 以及相关 API 这意味着您已经学习了许多有关如何直接使用 Service Worker 来开始摆弄 Workbox 的知识!