使用Service Worker缓存文件

2021-03-29  本文已影响0人  魂斗驴

Service Worker中使用缓存API

Service Worker API带有 Cache接口,该接口可让您创建按请求键控的响应存储。尽管此接口是为service workers设计的,但实际上它是暴露在窗口中的,可以从脚本中的任何位置进行访问。入口点是caches

您负责实现脚本(Service Worker)如何处理对缓存的更新。必须明确请求对缓存中所有项目的更新;项不会过期,必须删除。但是,如果缓存的数据量超过浏览器的存储限制,则浏览器将开始逐出与一个起点(一次一个起点)关联的所有数据,直到存储量再次低于该极限。有关更多信息,请参见 浏览器存储限制和限制条件

储存资源

在本节中,我们概述了一些用于缓存资源的常见模式: Service Worker安装用户交互网络响应

安装时-缓存应用程序框架

如果install启动service worker ,我们可以缓存HTML,CSS,JS以及组成应用程序框架的任何静态文件:

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(cacheName).then(function(cache) {
      return cache.addAll(
        [
          '/css/bootstrap.css',
          '/css/main.css',
          '/js/bootstrap.min.js',
          '/js/jquery.min.js',
          '/offline.html'
        ]
      );
    })
  );
});

首次安装Service Worker时,将触发此事件侦听器。

注意:,在发生此事件时,任何以前版本的Service Worker仍在运行并提供页面,因此您在此处所做的事情一定不能破坏它。例如,这不是删除旧缓存的好地方,因为以前的Service Worker此时可能仍在使用它们。

event.waitUntil延长install事件的生存期,直到传递的承诺成功解决为止。如果承诺被拒绝,则安装被视为失败,并且该服务工作程序被放弃(如果运行的是旧版本,它将保持活动状态)。

cache.addAll如果任何资源无法缓存,将拒绝。这意味着仅当所有资源cache.addAll都已缓存时,Service Worker才会安装。

关于用户交互

如果无法使整个网站离线,则可以让用户选择他们希望离线使用的内容(例如,视频,文章或图片库)。

一种方法是为用户提供“稍后阅读”或“保存为离线”按钮。单击它后,从网络中获取您所需的内容并将其放入缓存中:

document.querySelector('.cache-article').addEventListener('click', function(event) {
  event.preventDefault();
  var id = this.dataset.articleId;
  caches.open('mysite-article-' + id).then(function(cache) {
    fetch('/get-article-urls?id=' + id).then(function(response) {
      // /get-article-urls returns a JSON-encoded array of
      // resource URLs that a given article depends on
      return response.json();
    }).then(function(urls) {
      cache.addAll(urls);
    });
  });
});

在上面的示例中,当用户单击带有cache-article该类的元素时,我们将获取商品ID,获取具有该ID的商品,并将该商品添加到缓存中。

注意: 缓存API在窗口对象上可用,这意味着您不需要让Service Worker将东西添加到缓存中。

关于网络响应

如果请求与缓存中的任何内容都不匹配,请从网络获取请求,将其发送到页面,然后同时将其添加到缓存中。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return cache.match(event.request).then(function (response) {
        return response || fetch(event.request).then(function(response) {
          cache.put(event.request, response.clone());
          return response;
        });
      });
    })
  );
});

这种方法最适合经常更新的资源,例如用户的收件箱或文章内容。这对于非必要内容(例如化身)也很有用,但需要小心。如果对一系列URL执行此操作,请注意不要膨胀原始存储-如果用户需要回收磁盘空间,则您不希望成为主要候选人。确保您摆脱了不再需要的缓存中的项目。

注意: 为了有效利用内存,您只能读取一次响应/请求的正文。在上面的代码中,.clone()用于创建可单独读取的响应副本。

从缓存中提取文件

要提供缓存中的内容并使您的应用离线使用,您需要拦截网络请求并使用缓存中存储的文件进行响应。有几种方法可以解决此问题:

仅缓存

您不需要经常专门处理这种情况。 缓存回落到网络通常是适当的方法。

这种方法适用于应用程序主代码(应用程序“版本”的一部分)中的任何静态资产。您应该已经将它们缓存在install事件中,因此您可以依靠它们。

self.addEventListener('fetch', function(event) {
  event.respondWith(caches.match(event.request));
});

如果在缓存中找不到匹配项,则响应将看起来像是连接错误。

仅网络

对于无法离线执行的操作(例如分析ping和非GET请求),这是正确的方法。同样,您不必经常专门处理这种情况,并且 缓存回落到网络方法通常更合适。

self.addEventListener('fetch', function(event) {
  event.respondWith(fetch(event.request));
});

或者,只需不调用event.respondWith,这将导致默认的浏览器行为。

缓存回落到网络

如果您要先使应用程序离线,那么这就是您处理大多数请求的方式。其他模式将是基于传入请求的异常。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    })
  );
});

这使您对缓存中的内容具有“仅缓存”行为,对于未缓存的任何事物(包括所有非GET请求,因为它们无法缓存),其为“仅网络”行为。

网络回退到缓存

对于频繁更新且不属于网站“版本”一部分的资源(例如,文章,头像,社交媒体时间表,游戏排行榜),这是一种很好的方法。以这种方式处理网络请求意味着在线用户获得最新内容,而离线用户获得较旧的缓存版本。

但是,这种方法有缺陷。如果用户的连接是间歇性的或速度较慢,则他们必须等待网络出现故障,然后才能从缓存中获取内容。这可能会花费很长时间,并且是令人沮丧的用户体验。有关更好的解决方案,请参见下一种方法,即“先 缓存然后是网络”

self.addEventListener('fetch', function(event) {
  event.respondWith(
    fetch(event.request).catch(function() {
      return caches.match(event.request);
    })
  );
});

在这里,我们首先使用将该请求发送到网络fetch(),只有在失败时,我们才会在缓存中寻找响应。

缓存然后网络

对于频繁更新的资源,这也是一个很好的方法。这种方法将尽可能快地将内容显示在屏幕上,但到达后仍会显示最新内容。

这要求页面发出两个请求:一个请求到缓存,另一个请求到网络。这个想法是先显示缓存的数据,然后在/如果网络数据到达时更新页面。

这是页面中的代码:

var networkDataReceived = false;

startSpinner();

// fetch fresh data
var networkUpdate = fetch('/data.json').then(function(response) {
  return response.json();
}).then(function(data) {
  networkDataReceived = true;
  updatePage(data);
});

// fetch cached data
caches.match('/data.json').then(function(response) {
  if (!response) throw Error("No data");
  return response.json();
}).then(function(data) {
  // don't overwrite newer network data
  if (!networkDataReceived) {
    updatePage(data);
  }
}).catch(function() {
  // we didn't get cached data, the network is our last hope:
  return networkUpdate;
}).catch(showErrorMessage).then(stopSpinner());

我们正在向网络和缓存发送请求。缓存很可能会首先响应,如果尚未接收到网络数据,我们将使用响应中的数据更新页面。当网络响应时,我们将使用最新信息再次更新页面。

这是Service Worker中的代码:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return fetch(event.request).then(function(response) {
        cache.put(event.request, response.clone());
        return response;
      });
    })
  );
});

这将在获取网络响应时对其进行缓存。

有时,您可以在新数据到达时替换当前数据(例如,游戏排行榜),但请注意不要隐藏或替换用户可能正在与之交互的内容。例如,如果从缓存中加载博客文章页面,然后在从网络中获取新文章时将其添加到页面顶部,则可以考虑调整滚动位置,以使用户不受干扰。如果您的应用程序布局是线性的,那么这可能是一个很好的解决方案。

通用回落

如果您无法通过缓存和/或网络提供服务,则可能需要提供通用的备用。此技术非常适合辅助图像,例如头像,POST请求失败,“离线时不可用”页面。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    // Try the cache
    caches.match(event.request).then(function(response) {
      // Fall back to network
      return response || fetch(event.request);
    }).catch(function() {
      // If both fail, show a generic fallback:
      return caches.match('/offline.html');
      // However, in reality you'd have many different
      // fallbacks, depending on URL & headers.
      // Eg, a fallback silhouette image for avatars.
    })
  );
});

您回退到的项目可能是安装依赖项。

您还可以根据网络错误提供不同的后备:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    // Try the cache
    caches.match(event.request).then(function(response) {
      if (response) {
        return response;
      }
      return fetch(event.request).then(function(response) {
        if (response.status === 404) {
          return caches.match('pages/404.html');
        }
        return response
      });
    }).catch(function() {
      // If both fail, show a generic fallback:
      return caches.match('/offline.html');
    })
  );
});

网络响应错误不会在fetch承诺中引发错误。而是fetch返回包含网络错误的错误代码的响应对象。这意味着我们用a.then代替a处理网络错误.catch

删除过时的缓存

一旦安装了新的Service Worker并且未使用以前的版本,则将激活新的Service Worker,并且您会收到一个activate事件。由于旧版本已无法使用,因此现在是删除未使用的缓存的好时机。

self.addEventListener('activate', function(event) {
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.filter(function(cacheName) {
          // Return true if you want to remove this cache,
          // but remember that caches are shared across
          // the whole origin
        }).map(function(cacheName) {
          return caches.delete(cacheName);
        })
      );
    })
  );
});

在激活期间,其他事件(例如,fetch放入队列中),因此长时间激活可能会阻止页面加载。保持激活尽可能精简,仅将其用于旧版本处于活动状态时无法执行的操作。

使用缓存API

在这里,我们介绍了Cache API的属性和方法。

检查支持

我们可以检查浏览器是否支持Cache API,如下所示:

if ('caches' in window) {
  // has support
}

创建缓存

一个源可以有多个命名的Cache对象。要创建缓存或打开与现有缓存的连接,我们使用的 caches.open方法。

caches.open(cacheName)

这将返回一个承诺,该承诺将解析为缓存对象。caches.open接受将是缓存名称的字符串。

处理数据

缓存API带有多种方法,可让我们在缓存中创建和操作数据。这些可以分为创建,匹配或删除数据的方法。

建立资料

我们可以使用三种方法将数据添加到缓存。这些 addaddAllput。实际上,我们将在从返回的缓存对象上调用这些方法caches.open()。例如:

caches.open('example-cache').then(function(cache) {
        cache.add('/example-file.html');
});

Caches.open返回example-cacheCache对象,该对象将传递到中的回调.then。我们add在此对象上调用方法以将文件添加到该缓存。

cache.add(request)-add方法获取URL,对其进行检索,然后将所得的响应对象添加到给定的缓存中。该对象的关键是请求,因此我们稍后可以通过该请求再次检索该响应对象。

cache.addAll(requests)-此方法与add相同,除了它采用URL数组并将其添加到缓存中。如果无法将任何文件添加到缓存中,则整个操作将失败,并且不会添加任何文件。

cache.put(request, response)-此方法同时接收请求和响应对象,并将它们添加到缓存中。这使您可以手动插入响应对象。通常,您只需要fetch()一个或多个请求,然后将结果直接添加到缓存中。在这种情况下,最好cache.add还是使用或cache.addAll,因为它们是以下一项或多项操作的简写功能:

fetch(url).then(function (response) {
  return cache.put(url, response);
})

匹配数据

有两种方法可以在缓存中搜索特定内容: matchmatchAll。可以在caches对象上调用这些,以搜索所有现有的缓存,也可以在从返回的特定缓存上进行搜索caches.open()

caches.match(request, options)-此方法返回一个Promise,该Promise解析为与一个或多个缓存中的第一个匹配请求关联的响应对象。undefined如果找不到匹配项,则返回。第一个参数是请求,第二个参数是用于优化搜索的可选选项列表。这是MDN定义的选项:

caches.matchAll(request, options)-此方法与.match其他方法相同,只是它从缓存中返回所有匹配的响应,而不仅仅是第一个。例如,如果您的应用程序缓存了图像文件夹中包含的某些图像,我们可以返回所有图像并对它们执行一些操作,如下所示:

caches.open('example-cache').then(function(cache) {
  cache.matchAll('/images/').then(function(response) {
    response.forEach(function(element, index, array) {
      cache.delete(element);
    });
  });
})

删除资料

我们可以使用删除缓存中的项目cache.delete(request, options)。此方法在缓存中找到与请求匹配的项目,将其删除,然后返回解析为的Promise true。如果找不到该项目,则将其解析为false。它还具有与match方法相同的可选options参数。

检索密钥

最后,我们可以使用获取缓存键列表cache.keys(request, options)。这将返回一个Promise,该Promise解析为一组缓存键。这些将以插入缓存中的相同顺序返回。这两个参数都是可选的。如果未传递任何内容,则cache.keys返回缓存中的所有请求。如果传递了请求,它将从缓存中返回所有匹配的请求。这些选项与先前方法中的选项相同。

还可以在缓存入口点上调用keys方法,以返回缓存本身的密钥。这使您可以一次性清除过时的缓存。

参考

Caching Files with Service Worker

上一篇下一篇

猜你喜欢

热点阅读