Progressive Web Apps(PWA)核心技术-Cache API

Service Worker API带有一个Cache接口,可以让您创建按请求键入的响应存储。 缓存资源的常见模式有:service worker安装、用户交互、网络响应。

1、在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可能仍然在使用它们。

2.用户交互时

如果整个网站无法脱机,则可以让用户选择要脱机使用的内容(例如,视频,文章或照片库)。

一种方法是给用户一个“稍后阅读”或“保存离线”按钮。 点击时,从网络中获取所需内容并将其放入缓存中:

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);
    });
  });
});

注意:缓存API在window对象上可用,这意味着您不需要service worker将内容添加到缓存中。


3.在网络响应时

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

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;
        });
      });
    })
  );
});

这种方法最适合频繁更新的资源,例如用户的收件箱或文章内容。 这对非必要的内容(例如头像)也是有用的,但需要小心。


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


从缓存中提取文件

要从缓存提供内容并使您的应用脱机,您需要拦截网络请求并使用缓存中存储的文件进行响应。 有几种方法:

  • cache only
  • network only
  • cache falling back to network
  • network falling back to cache
  • cache then network

仅从缓存中
你不需要经常专门处理这个文件。回退到网络的缓存通常是适当的方法。

这种方法适用于作为应用程序主代码(应用程序的“版本”的一部分)的任何静态资源。你应该在安装事件中缓存这些,所以你可以在那里依赖它们。

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);
    })
  );
});

这为缓存中的内容提供了“Cache only”行为,而对于任何未缓存的内容(包括所有非GET请求,因为它们不能被缓存),“Network only”行为都是如此。

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

但是,这种方法有缺陷。如果用户连接中断或连接速度较慢,则在从缓存中获取内容之前,必须等待网络请求失败。这可能需要很长时间,并且是令人沮丧的用户体验。查看下一个方法,缓存然后网络,以获得更好的解决方案。

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 promise中抛出错误。 相反,fetch返回包含网络错误的错误代码的响应对象。 这意味着我们在.then而不是.catch中处理网络错误。

删除过期的缓存

一旦安装了新的service worker并且以前的版本没有被使用,新的service worker就会激活,并且您将获得一个激活事件。 由于旧版本没有被使用,现在是删除未使用的缓存的好时机。

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)被放入队列中,所以长时间的激活可能会阻止页面加载。 保持激活尽可能精简,只用于旧版本激活时无法执行的操作。

参考链接:https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker#using_the_cache_api

你可能感兴趣的:(Progressive Web Apps(PWA)核心技术-Cache API)