命令式缓存指南

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

有些网站可能需要与 Service Worker 通信, 获知结果。下面是一些示例:

  • 页面向 Service Worker 发送一个网址列表, 预提取的内容,以便在用户点击 链接了缓存中已经可用的文档或页面子资源,使得后续 大大加快导航速度
  • 页面要求 Service Worker 检索并缓存一组热门文章, 以供离线使用

将这些类型的非关键任务委托给 Service Worker 的好处是, 主线程,以便更好地处理更紧迫的任务,例如响应用户交互。

一个页面请求将资源缓存到 Service Worker 的示意图。

在本指南中,我们将探讨如何在网页和各个环节之间实现单向通信技术。 标准浏览器 API 和 Workbox 库来运行 Service Worker。我们将这些类型的 命令式缓存使用场景。

制作案例

1-800-Flowers.com 通过以下方式通过 Service Worker 实现命令式缓存(预提取): postMessage(),用于预取 热门商品,以加快后续导航到商品详情页面的速度。

1-800 Flowers 的徽标。

它们使用混合方法来决定要预提取哪些项:

  • 在页面加载时,它们要求 Servicer Worker 检索前 9 项的 JSON 数据,并且 将生成的响应对象添加到缓存中。
  • 对于其余项,它们会监听 mouseover 事件触发 当用户将光标移到某项内容上方时,他们可以“按需”触发对该资源的提取。

它们使用 Cache API 存储 JSON 响应:

<ph type="x-smartling-placeholder">
</ph> 1-800 Flowers 的徽标。
从 1-800Flowers.com 的商品详情页面预提取 JSON 商品数据。

当用户点击某个商品时,可以从缓存中获取与其关联的 JSON 数据, 也无需连接到网络,从而加快导航速度。

使用 Workbox

Workbox 可让您轻松地将邮件发送至 一个 Service Worker,通过 workbox-window 软件包提供, 旨在在窗口上下文中运行的应用。它们是对其他 Workbox 软件包的补充 在 Service Worker 中运行的应用

要与 Service Worker 通信页面,请先获取指向 注册的 Service Worker:

const wb = new Workbox('/sw.js');
wb.register();

然后,您可以直接以声明方式发送消息,免去获取 注册、检查激活情况或考虑底层通信 API:

wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });

Service Worker 实现 message 处理程序, 收听这些消息。它可以选择性地返回响应 不需要:

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PREFETCH') {
    // do something
  }
});

使用浏览器 API

如果 Workbox 库无法满足您的需求,您可以通过以下方式实现窗口到服务 通过浏览器 API 实现工作器通信。

postMessage API 可用于建立从页面到 Service Worker 的单向通信机制。

网页调用 postMessage()(位于 Service Worker 接口:

navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
  payload: 'some data to perform the task',
});

Service Worker 实现 message 处理程序, 收听这些消息。

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === MSG_ID) {
    // do something
  }
});

{type : 'MSG_ID'} 属性并非绝对必要,但可让网页 向 Service Worker 发送不同类型的指令(即“预提取”与“清除” storage')。Service Worker 可以根据此标志分支到不同的执行路径。

如果操作成功,用户将能够从中受益,但是如果没有,则不会影响主要的用户流。例如,当 1-800-Flowers.com 尝试预缓存时,页面无需知道 Service Worker 是否成功。如果支持,用户将享受到更快速的导航体验。如果没有,页面仍然需要导航到新页面。这需要多一点时间。

简单的预提取示例

命令式缓存的最常见应用之一是预提取,这意味着 从而加快导航速度。

您可以通过以下不同方式在网站中实现预提取:

  • 在网页中使用链接预提取代码:资源会保存在 浏览器缓存五分钟,之后系统会对资源应用正常的 Cache-Control 规则 。
  • 通过服务中的运行时缓存策略对之前的技术进行补充 worker 来延长预提取的生命周期 超过此限制的资源。

对于相对简单的预提取场景,例如预提取文档或特定资源(JS、 CSS 等),那么这些技术是最佳方法。

如果需要其他逻辑,例如在 为了提取其内部网址,更合适的做法是将此任务完全委托给 Service Worker。

将这些类型的操作委托给 Service Worker 具有以下优势:

  • 分流了抓取和抓取后处理的繁重工作(我们将介绍 发送到辅助线程。这样就能释放主线程来处理更重要的任务 例如响应用户互动
  • 允许多个客户端(如标签页)重复使用一项常用功能,甚至可以调用 而无需阻塞主线程。

预提取商品详情页面

首次使用 postMessage()的日期: Service Worker 接口,并传递要缓存的网址数组:

navigator.serviceWorker.controller.postMessage({
  type: 'PREFETCH',
  payload: {
    urls: [
      'www.exmaple.com/apis/data_1.json',
      'www.exmaple.com/apis/data_2.json',
    ],
  },
});

在 Service Worker 中,实现 message 处理程序 拦截和处理任何活动标签页发送的消息:

addEventListener('message', (event) => {
  let data = event.data;
  if (data && data.type === 'PREFETCH') {
    let urls = data.payload.urls;
    for (let i in urls) {
      fetchAsync(urls[i]);
    }
  }
});

在之前的代码中,我们引入了一个名为 fetchAsync() 的小型辅助函数,用于迭代 网址数组,并针对每个网址发出提取请求:

async function fetchAsync(url) {
  // await response of fetch call
  let prefetched = await fetch(url);
  // (optionally) cache resources in the service worker storage
}

获取响应后,您可以依赖资源的缓存标头。在很多情况下 但和商品详情页面一样,资源不会被缓存(这意味着它们 no-cacheCache-control 标头)。在此类情况下,您可以替换此行为, 将提取的资源存储在 Service Worker 缓存中。这样做还有一个好处,那就是 文件。

不局限于 JSON 数据

从服务器端点提取 JSON 数据后,这些数据通常会包含 例如,与这一第一级相关联的图片或其他端点数据。 数据。

假设在我们的示例中,返回的 JSON 数据是某个杂货店购物网站的信息:

{
  "productName": "banana",
  "productPic": "https://cdn.example.com/product_images/banana.jpeg",
  "unitPrice": "1.99"
 }

修改 fetchAsync() 代码,以遍历商品列表并缓存主打图片 分别为

async function fetchAsync(url, postProcess) {
  // await response of fetch call
  let prefetched = await fetch(url);

  //(optionally) cache resource in the service worker cache

  // carry out the post fetch process if supplied
  if (postProcess) {
    await postProcess(prefetched);
  }
}

async function postProcess(prefetched) {
  let productJson = await prefetched.json();
  if (productJson && productJson.product_pic) {
    fetchAsync(productJson.product_pic);
  }
}

对于 404 等情况,您可以在此代码周围添加一些异常处理。 但使用 Service Worker 进行预提取的优点是它可能会失败且不会太多 对网页和主线程造成的不良影响您在本单元的 对预提取内容进行后处理,使其更加灵活,并且与 处理。讨论内容海阔天空。

总结

在本文中,我们介绍了页面与服务之间的单向通信的常见用例。 工作器:命令式缓存。这些示例仅用于演示 使用这种模式,同样的方法也可应用于其他用例,例如, 按需缓存热门文章以供离线阅读、添加书签等。

如需了解页面和 Service Worker 的更多通信模式,请参阅:

  • 广播更新:从 Service Worker 调用页面以通知 关于重要更新的信息(例如,有新版本的 Web 应用)。
  • 双向通信:将任务委托给 Service Worker(例如 大量下载),并随时让网页了解进度。