正在处理流量

渐进式 Web 应用的一个关键方面是它们可靠;即使在网络状况不佳时,它们也可以快速加载资源,持续吸引用户并立即提供反馈。为什么会这样?感谢 Service Worker fetch 事件。

fetch 事件

浏览器支持

  • 40
  • 17
  • 44
  • 11.1

来源

借助 fetch 事件,我们可以针对同源和跨源请求,拦截 PWA 在 Service Worker 作用域内发出的每个网络请求。除了导航和资产请求之外,通过已安装的 Service Worker 提取网页还允许在网站首次加载后呈现网页访问,而不进行网络调用。

fetch 处理程序接收来自应用的所有请求(包括网址和 HTTP 标头),并让应用开发者决定如何处理这些请求。

Service Worker 位于客户端和网络之间。

您的 Service Worker 可以将请求转发给网络,使用先前缓存的响应进行响应,或者创建新的响应。如何选择,完全取决于您。 下面是一个简单的示例:

self.addEventListener("fetch", event => {
    console.log(`URL requested: ${event.request.url}`);
});

响应请求

当请求进入您的 Service Worker 时,您可以采取两种措施:您可以忽略该请求,让请求进入网络;或者,您也可以进行响应。通过在 Service Worker 中响应请求,您可以选择哪些内容以及如何将其返回给 PWA,即使用户处于离线状态也是如此。

如需响应传入请求,请在 fetch 事件处理脚本中调用 event.respondWith(),如下所示:

// fetch event handler in your service worker file
self.addEventListener("fetch", event => {
    const response = .... // a response or a Promise of response
    event.respondWith(response);
});

您必须同步调用 respondWith(),并且必须返回一个 Response 对象。但是,您不能在提取事件处理脚本完成后调用 respondWith(),比如在异步调用中。如果您需要等待完整响应,可以将 Promise 传递给使用 Response 进行解析的 respondWith()

创建响应

得益于 Fetch API,您可以在 JavaScript 代码中创建 HTTP 响应,并且这些响应可以使用 Cache Storage API 进行缓存,并像来自网络服务器一样返回。

如需创建响应,请创建一个新的 Response 对象,设置其正文以及状态和标头等选项:

const simpleResponse = new Response("Body of the HTTP response");

const options = {
   status: 200,
   headers: {
    'Content-type': 'text/html'
   }
};
const htmlResponse = new Response("<b>HTML</b> content", options)

从缓存响应

现在,您已经知道如何通过 Service Worker 提供 HTTP 响应,接下来可以使用缓存存储接口在设备上存储资源。

您可以使用 Cache Storage API 检查从 PWA 收到的请求在缓存中是否可用,如果可用,则用它响应 respondWith()。 为此,您首先需要在缓存内进行搜索。match() 函数(位于顶级 caches 界面中)可搜索源站中的所有存储区,或搜索单个打开的缓存对象。

match() 函数接收 HTTP 请求或网址作为参数,并返回使用与相应键关联的响应进行解析的 promise。

// Global search on all caches in the current origin
caches.match(urlOrRequest).then(response => {
   console.log(response ? response : "It's not in the cache");
});

// Cache-specific search
caches.open("pwa-assets").then(cache => {
  cache.match(urlOrRequest).then(response => {
    console.log(response ? response : "It's not in the cache");
  });
});

缓存策略

仅从浏览器缓存传送文件并不适合所有用例。例如,用户或浏览器可以逐出缓存。因此,您应该定义自己的策略来为 PWA 分发资源。您不限于一种缓存策略。您可以为不同的网址格式定义不同的网址。例如,您可以为最少界面资源使用一种策略,针对 API 调用使用另一种策略,第三种策略用于图片和数据网址。为此,请阅读 ServiceWorkerGlobalScope.onfetch 中的 event.request.url,并通过正则表达式或网址格式对其进行解析。(在撰写本文时,并非所有平台都支持网址格式)。

最常用的策略包括:

缓存优先
首先搜索缓存的响应,如果未找到,则回退到网络。
广告联盟优先
首先从网络请求响应,如果未返回任何响应,则在缓存中检查响应。
重新验证时过时
从缓存提供响应,同时在后台请求最新版本并将其保存到缓存中,以便下次请求资源时使用。
仅限网络
一律使用网络回复或出错。但系统永远不会查询缓存。
仅缓存
始终使用缓存返回的响应或输出错误进行回复。系统绝不会查询广告网络。使用此策略提供的资源必须先添加到缓存中,然后再请求这些资源。

缓存优先

使用此策略,Service Worker 在缓存中查找匹配的请求,如果缓存已缓存,则返回相应的 Response。否则,它将从网络中检索响应(可选择更新缓存以用于将来的调用)。如果既没有缓存响应也没有网络响应,则请求将会出错。由于不向广告网络投放素材资源的速度往往更快,因此该策略优先考虑效果而非新鲜度。

缓存优先策略

self.addEventListener("fetch", event => {
   event.respondWith(
     caches.match(event.request)
     .then(cachedResponse => {
       // It can update the cache to serve updated content on the next request
         return cachedResponse || fetch(event.request);
     }
   )
  )
});

网络优先

此策略是“缓存优先”策略的镜像;它会检查是否可以从网络满足请求,如果不能满足,就会尝试从缓存中检索。像优先缓存一样。如果既没有网络响应也没有缓存响应,则请求将会出错。从网络获取响应通常比从缓存中获取响应速度慢,此策略会优先考虑更新的内容而不是性能。

“网络优先”策略

self.addEventListener("fetch", event => {
   event.respondWith(
     fetch(event.request)
     .catch(error => {
       return caches.match(event.request) ;
     })
   );
});

重新验证时过时

过时的“重新验证”策略会立即返回缓存的响应,然后检查网络是否有更新,如果发现缓存响应,则会替换该响应。此策略始终会发出网络请求,因为即使找到了缓存的资源,它也会尝试使用从网络接收的内容更新缓存中的内容,以便在下一个请求中使用更新后的版本。因此,此策略可让您从缓存优先策略的快速提供中受益,并在后台更新缓存。

重新验证策略时已过时

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cachedResponse => {
        const networkFetch = fetch(event.request).then(response => {
          // update the cache with a clone of the network response
          const responseClone = response.clone()
          caches.open(url.searchParams.get('name')).then(cache => {
            cache.put(event.request, responseClone)
          })
          return response
        }).catch(function (reason) {
          console.error('ServiceWorker fetch failed: ', reason)
        })
        // prioritize cached response over network
        return cachedResponse || networkFetch
      }
    )
  )
})

仅限网络

“仅限网络”策略类似于浏览器在不使用 Service Worker 或 Cache Storage API 的情况下的行为。只有在可以从网络中提取资源时,请求才会返回资源。这通常对于仅在线 API 请求等资源非常有用。

“仅限广告网络”策略

仅缓存

“仅缓存”策略可确保请求永远不会进入网络;所有传入请求均以预填充的缓存项作为响应。以下代码结合使用 fetch 事件处理脚本和缓存存储空间的 match 方法,以便仅响应缓存:

self.addEventListener("fetch", event => {
   event.respondWith(caches.match(event.request));
});

仅缓存策略。

自定义策略

虽然以上是常用的缓存策略,但您负责管理 Service Worker 以及请求的处理方式。如果上述方法均无法满足您的需求,请自行创建。

例如,您可以使用带有超时的“广告联盟优先”策略来优先考虑更新的内容,但前提是相应响应未超出您设置的阈值。您还可以将缓存的响应与网络响应合并,并通过 Service Worker 构建复杂的响应。

正在更新资产

使 PWA 的缓存资源保持最新状态是一项挑战。虽然在重新验证策略时过时只是其中一种方法,但并非唯一的方法。在更新章节中,您将了解更新应用内容和资源的不同技巧。

资源