Service Worker 全面进阶

腾讯云技术社区-掘金主页持续为你们呈现云计算技术文章,欢迎你们关注!javascript


做者:villainthrcss

Service Worder 是用来代替 manifest,用来生成缓存的效果的。之前吭哧吭哧的学 manifest 的时候,就发现 MD 好难用。并且 MDN 特地告诉你,manifest 有毒,请不要乱用,保不定后面不支持。今儿,我看了下兼容性,呵呵~html

hehe

人生苦短,及时享乐,前端真坑,不敢乱学。前端

前方高能,若是以为生活没有趣味能够继续看下去,会让你的人生更没有趣味。若是以为凑合能过,请 ctrl/command + wjava

继续~node

Service Worker 讲道理是由两部分构成,一部分是 cache,还有一部分则是 Worker。因此,SW(Service Worker) 自己的执行,就彻底不会阻碍当前 js 进程的执行,确保性能第一。那 SW 究竟是怎么工做的呢?git

  • 后台进程: SW 就是一个 worker 独立于当前网页进程。
  • 网络代理: SW 能够用来代理请求,缓存文件
  • 灵活触发: 须要的时候吊起,不须要的时候睡眠(这个是个坑)
  • 异步控制: SW 内部使用 promise 来进行控制。

咱们先来看看 SW 比较坑的地方,它的 lifecyclegithub

SW 的生命周期

首先,SW 并非你网页加载就与生俱来的。若是,你须要使用 SW,你首先须要注册一个 SW,让浏览器为你的网页分配一块内存空间来。而且,你可否注册成功,还须要看你缓存的资源量决定(有可能失败,真的有可能)。若是,你须要缓存的静态资源所有保存成功,那么恭喜您,SW 安装成功。若是,其中有一个资源下载失败而且没法缓存,那么此次吊起就是失败的。不过,SW 是由重试机制的,这点也不算特别坑。web

当安装成功以后,此时 SW 就进入了激活阶段(activation)。而后,你能够选择性的检查之前的文件是否过时等。ajax

检查完以后,SW 就进入待机状态。此时,SW 有两种状态,一种是 active,一种是 terminated。就是激活/睡眠。激活是为了工做,睡眠则为了节省内存。这是一开始设计的初衷。若是,SW 已经 OK,那么,你网页的资源都会被 SW 控制,固然,SW 第一次加载除外。
简单的流程图,能够参考一下 google的:

google_fc

从入门到放弃

上面简单介绍了 SW 的基本生命周期(实际上,都是废话),讲点实在的,它的兼容性咋样?

compa

基本上手机端是能用的。

基于 HTTPS

如今,开发一个网站没用 HTTPS,估计都没好意思放出本身的域名(太 low)。HTTPS 不只仅能够保证你网页的安全性,还可让一些比较敏感的 API 完美的使用。值得一提的是,SW 是基于 HTTPS 的,因此,若是你的网站不是 HTTPS,那么基本上你也别想了 SW。这估计形成了一个困难,即,我调试 SW 的时候咋办?
解决办法也是有的,使用 charles 或者 fildder 完成域名映射便可。

下面,咱们仔细介绍下,SW 的基本使用。

Register

SW 其实是挂载到 navigator 下的对象。在使用以前,咱们须要先检查一下是否可用:

if ('serviceWorker' in navigator) {
  // ....
}复制代码

若是可用,咱们就要使用 SW 进行路由的注册缓存文件了。不过,这里有点争议。啥时候开始执行 SW 的注册呢?上面说过,SW 就是一个网络代理,用来捕获你网页的全部 fetch 请求。那么,是否是能够这么写?

window.addEventListener('DOMContentLoaded', function() {
    // 执行注册
    navigator.serviceWorker.register('/sw.js').then(function(registration) {

    }).catch(function(err) {

    }); 
  });复制代码

这样理解逻辑上是没有任何问题的,关键在于,虽然 SW 是 worker ,但浏览器的资源也是有限的,浏览器分配给你网页的内存就这么多,你再开个 SW(这个很大的。。。),没有 jank 才怪嘞,并且若是你网页在一开始加载的时候有动画展现的话,那么这种方式基本上就 GG 了。
另外,若是算上用户第一次加载,那么这个卡顿或者延时就很大了。
固然,W3C 在制定相关规范时,确定考虑到这点,实际上 SW 在你网页加载完成一样也能捕获已经发出的请求。因此,为了减小性能损耗,咱们通常直接在 onload 事件里面注册 SW 便可。GOOGLE Jeff Posnick 针对这个加载,专门讨论了一下,有兴趣的能够参考一下。(特别提醒,若是想要测试注册 SW 可使用隐身模式调试!!!)
那当我注册成功时,怎样查看我注册的 SW 呢?
这很简单,直接打开 chrome://inspect/#service-workers 就能够查看,在当前浏览器中,正在注册的 SW。另外,还有一个 chrome://serviceworker-internals,用来查看当前浏览器中,全部注册好的 SW。
使用 SW 进行注册时,还有一个很重要的特性,即,SW 的做用域不一样,监听的 fetch 请求也是不同的。
例如,咱们将注册路由换成: /example/sw.js

window.addEventListener('DOMContentLoaded', function() {
    // 执行注册
    navigator.serviceWorker.register('/example/sw.js').then(function(registration) {

    }).catch(function(err) {

    });
  });复制代码

那么,SW 后面只会监听 /example 路由下的全部 fetch 请求,而不会去监听其余,好比 /jimmy,/sam 等路径下的。

Install

从这里开始,咱们就正式进入 SW 编程。记住,下面的部分是在另一个 js 中的脚本,使用的是 worker 的编程方法。若是,有同窗还不理解 worker 的话,能够先去学习一下,这样在后面的学习中才不会踩很深的坑。
监听安装 SW 的代码也很简单:

self.addEventListener('install', function(event) {
  // Perform install steps
});复制代码

当安装成功后,咱们能使用 SW 作什么呢?
那就开始缓存文件了呗。简单的例子为:

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('mysite-static-v1').then(function(cache) {
      return cache.addAll([
        '/css/whatever-v3.css',
        '/css/imgs/sprites-v6.png',
        '/css/fonts/whatever-v8.woff',
        '/js/all-min-v4.js'
      ]);
    })
  );
});复制代码

此时,SW 会检测你制定文件的缓存问题,若是,已经都缓存了,那么 OK,SW 安装成功。若是查到文件没有缓存,则会发送请求去获取,而且会带上 cache-bust 的 query string,来表示缓存的版本问题。固然,这只针对于第一次加载的状况。当全部的资源都已经下载成功,那么恭喜你能够进行下一步了。你们能够参考一下 google demo
这里,我简单说一下上面的过程,首先 event.waitUntil 你能够理解为 new Promise,它接受的实际参数只能是一个 promise,由于,caches 和 cache.addAll 返回的都是 Promise,这里就是一个串行的异步加载,当全部加载都成功时,那么 SW 就能够下一步。另外,event.waitUntil 还有另一个重要好处,它能够用来延长一个事件做用的时间,这里特别针对于咱们 SW 来讲,好比咱们使用 caches.open 是用来打开指定的缓存,但开启的时候,并非一下就能调用成功,也有可能有必定延迟,因为系统会随时睡眠 SW,因此,为了防止执行中断,就须要使用 event.waitUntil 进行捕获。另外,event.waitUntil 会监听全部的异步 promise,若是其中一个 promise 是 reject 状态,那么该次 event 是失败的。这就致使,咱们的 SW 开启失败。

不稳定加载

不过,若是其中一个文件下载失败的话,那么此次你的 SW 启动就告吹了,即,若是其中有一个 Promise 是使用 reject 的话,那就表明着--您此次启动是 GG 的。那,有没有其余办法在保证必定稳定性的前提下,去加载比较大的文件呢?
有的,那你别返回 cache.addAll 就ok了。什么个意思呢?
就这样:

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('mygame-core-v1').then(function(cache) {
    // 不稳定文件或大文件加载
      cache.addAll(
        //...
      );
      // 稳定文件或小文件加载
      return cache.addAll(
        // core assets & levels 1-10
      );
    })
  );
});复制代码

这样,第一个 cache.addAll 是不会被捕获的,固然,因为异步的存在,这毋庸置疑会有一些问题。好比,当大文件还在加载的时候,SW 断开,那么此次请求就是无效的。不过,你这样写原本就算是一个 trick,这种状况在制定方案的时候,确定也要考虑进去的。整个步骤,咱们能够用下图表示:
FROM GOOGLE

from google

缓存捕获

该阶段就是事关整个网页可否正常打开的一个阶段--很是关键。在这一阶段,咱们将学会,如何让 web 使用缓存,如何作向下兼容。
先看一个简单的格式:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response;
        }
        return fetch(event.request);
      }
    )
  );
});复制代码

首先看一下,第一个方法--event.respondWith,用来包含响应主页面请求的代码。当接受到 fetch 请求时,会直接返回 event.respondWith Promise 结果。咱们在 worker 中,捕获页面全部的 fetch 请求。能够看到 event.request ,这个就是 fetch 的 request 流。咱们经过 caches.match 捕获,而后返回 Promise 对象,用来进行响应的处理。你们看这段代码时,可能会有不少的疑惑,是的,一开始我看的时候也是,由于,根本没注释,有些 name 其实是内核自带的。上面的就有:

  • caches: 这是用来控制缓存专门分离出来的一个对象。能够参考: caches
  • fetch: 是现代浏览器用来代替 XMLHttpRequest 专门开发出的 ajax 请求。能够参考: fetch 通讯

简单来讲,caches.match 根据 event.request,在缓存空间中查找指定路径的缓存文件,若是匹配到,那么 response 是有内容的。若是没有的话,则再经过 fetch 进行捕获。整个流图以下:

up_load

OK,那如今有个问题,若是没有找到缓存,那么应该怎么作呢?

  • 啥都不作,等下一次 SW 本身根据路由去缓存。
  • 没找到,我手动 fetch 而后添加进缓存。

那怎么手动添加呢?
很简单,本身发送 fetch,而后使用 caches 进行缓存便可。不过,这里又涉及到另一个概念,Request 和 Response 流。这是在 fetch 通讯方式 很重要的两个概念。fetch 不只分装了 ajax,并且在通讯方式上也作了进一步的优化,同 node 同样,使用流来进行重用。众所周知,一个流通常只能使用一次,能够理解为喝矿泉水,只能喝一次,不过,若是我知道了该水的配方,那么我就能够量产该水,这就是流的复制。下面代码也基本使用到这两个概念,基本代码为:

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

        // 由于 event.request 流已经在 caches.match 中使用过一次,
        // 那么该流是不能再次使用的。咱们只能获得它的副本,拿去使用。
        var fetchRequest = event.request.clone();

        // fetch 的经过信方式,获得 Request 对象,而后发送请求
        return fetch(fetchRequest).then(
          function(response) {
            // 检查是否成功
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

            // 若是成功,该 response 一是要拿给浏览器渲染,而是要进行缓存。
            // 不过须要记住,因为 caches.put 使用的是文件的响应流,一旦使用,
            // 那么返回的 response 就没法访问形成失败,因此,这里须要复制一份。
            var responseToCache = response.clone();

            caches.open(CACHE_NAME)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });

            return response;
          }
        );
      })
    );
});复制代码

那么整个流图变为:

fetch

而里面最关键的地方就是 stream 这是如今浏览器操做数据的一个新的标准。为了不将数据一次性写入内存,咱们这里引入了 stream,至关于一点一点的吐。这个和 nodeJS 里面的 stream 是同样的效果。你用上述哪一个流图,这估计得取决于你本身的业务。

Update

在 SW 中的更新涉及到两块,一个是基本静态资源的更新,还有一个是 SW.js 文件的更新。这里,咱们先说一下比较坑的 SW.js 的更新。

SW.js 的更新

SW.js 的更新不只仅只是简单的更新,为了用户可靠性体验,里面仍是有不少门道的。

  • 首先更新 SW.js 文件,这是最主要的。只有更新 SW.js 文件以后,以后的流程才能触发。SW.js 的更新也很简单,直接改动 SW.js 文件便可。浏览器会自动检查差别性(就算只有 1B 的差别也行),而后进行获取。
  • 新的 SW.js 文件开始下载,而且 install 事件被触发
  • 此时,旧的 SW 还在工做,新的 SW 进入 waiting 状态。注意,此时并不存在替换
  • 接着,当你如今已经打开的页面关闭时,那么旧的 SW 则会被 kill 掉。新的 SW 就开始接管页面的缓存资源。
  • 一旦新的 SW 接管,则会触发 activate 事件。

整个流程图为:

SW

若是上述步骤成功后,原来的 SW.js 就会被清除。可是,之前版本 SW.js 缓存文件没有被删除。针对于这一状况,咱们能够在新的 SW.js 里面监听 activate 事件,进行相关资源的删除操做。固然,这里主要使用到的 API 和 caches 有很大的关系(由于,如今全部缓存的资源都在 caches 的控制下了)。好比,我之前的 SW 缓存的版本是 v1,如今是 v2。那么我须要将 v1 给删除掉,则代码为:

self.addEventListener('activate', function(event) {

  var cacheWhitelist = ['v1'];

  event.waitUntil(
  // 遍历 caches 里全部缓存的 keys 值
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.includes(cacheName)) {
          // 删除 v1 版本缓存的文件
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});复制代码

另外,我那么你不经仅能够用来做为版本的更新,还能够做为缓存目录的替换。好比,我想直接将 site-v1的缓存文件,替换为 ajax-v1page-v1。则,咱们一是须要先在 install 事件里面将 ajajx-v1page-v1 缓存套件给注册了,而后,在 activate 里面将 site-v1 缓存给删除,实际代码和上面实际上是同样的:

self.addEventListener('activate', function(event) {

  var cacheWhitelist = ['site-v1'];

  event.waitUntil(
  // 遍历 caches 里全部缓存的 keys 值
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.includes(cacheName)) {
          // 删除 v1 版本缓存的文件
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});复制代码

OK,SW.js 更新差很少就是这样一块内容。

文件更新

对于文件更新来讲,整个机制就显得很简单了。能够说,你想要一个文件更新,只须要在 SW 的 fetch 阶段使用 caches 进行缓存便可。实际操做也很简单,一开始咱们的 install 阶段的代码为:

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('mysite-static-v1').then(function(cache) {
      return cache.addAll([
        '/css/whatever-v3.css',
        '/css/imgs/sprites-v6.png',
        '/css/fonts/whatever-v8.woff',
        '/js/all-min-v4.js'
      ]);
    })
  );
});复制代码

咱们只须要在这里简单的写下一下 prefetch 代码便可。

self.addEventListener('install', function(event) {
  var now = Date.now();
  // 事先设置好须要进行更新的文件路径
  var urlsToPrefetch = [
    'static/pre_fetched.txt',
    'static/pre_fetched.html',
    'https://www.chromium.org/_/rsrc/1302286216006/config/customLogo.gif'
  ];


  event.waitUntil(
    caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
      var cachePromises = urlsToPrefetch.map(function(urlToPrefetch) {
      // 使用 url 对象进行路由拼接
        var url = new URL(urlToPrefetch, location.href);
        url.search += (url.search ? '&' : '?') + 'cache-bust=' + now;
        // 建立 request 对象进行流量的获取
        var request = new Request(url, {mode: 'no-cors'});
        // 手动发送请求,用来进行文件的更新
        return fetch(request).then(function(response) {
          if (response.status >= 400) {
            // 解决请求失败时的状况
            throw new Error('request for ' + urlToPrefetch +
              ' failed with status ' + response.statusText);
          }
          // 将成功后的 response 流,存放在 caches 套件中,完成指定文件的更新。
          return cache.put(urlToPrefetch, response);
        }).catch(function(error) {
          console.error('Not caching ' + urlToPrefetch + ' due to ' + error);
        });
      });

      return Promise.all(cachePromises).then(function() {
        console.log('Pre-fetching complete.');
      });
    }).catch(function(error) {
      console.error('Pre-fetching failed:', error);
    })
  );
});复制代码

当成功获取到缓存以后, SW 并不会直接进行替换,他会等到用户下一次刷新页面事后,使用新的缓存文件。

fileUpload

不过,这里请注意,我并无说,咱们更新缓存只能在 install 里更新,事实上,更新缓存能够在任何地方执行。它主要的目的是用来更新 caches 里面缓存套件。咱们提取一下代码:

// 找到缓存套件并打开
caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
        // 根据事先定义的路由开始发送请求
      var cachePromises = urlsToPrefetch.map(function(urlToPrefetch) {
        // 执行 fetch
        return fetch(request).then(function(response) {
          // 缓存请求到的资源
          return cache.put(urlToPrefetch, response);
        }).catch(function(error) {
          console.error('Not caching ' + urlToPrefetch + ' due to ' + error);
        });
      });
    // 使用 promise.all 进行所有捕获
      return Promise.all(cachePromises).then(function() {
        console.log('Pre-fetching complete.');
      });
    }).catch(function(error) {
      console.error('Pre-fetching failed:', error);
    })复制代码

如今,咱们已经拿到了核心代码,那有没有什么简便的办法,让咱们少写一些配置项,直接对每个文件进行文件更新教研。
有的!!!
还记得上面的 fetch 事件吗?咱们简单回顾一下它的代码:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response;
        }
        return fetch(event.request);
      }
    )
  );
});复制代码

实际上,咱们能够将上面的核心代码作一些变化直接用上:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return cache.match(event.request).then(function(response) {
        var fetchPromise = fetch(event.request).then(function(networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        })
        return response || fetchPromise;
      })
    })
  );
});复制代码

这里比较难的地方在于,咱们并无去捕获 fetch(fetchRequest)... 相关内容。也就是说,这一块是彻底独立于咱们的主体业务的。他的 fetch 只是用更新文件而已。咱们可使用一个流图进行表示:

prefetch

ok,关于文件的缓存咱们就介绍到这里。

用户更新

如今,为了更好的用户体验,咱们能够作的更尊重用户一些。能够设置一个 button,告诉用户是否选择缓存指定文件。有同窗可能会想到使用 postmessage API,来告诉 SW 执行相关的缓存信息。不过事实上,还有更简单的办法来完成,即,直接使用 caches 对象。caches 和 web worker 相似。都是直接挂载到 window 对象上的。因此,咱们能够直接使用 caches 这个全局变量来进行搜索。那么该环节就不须要直接经过 SW,这个流程图能够画为:

SW_

代码能够参考:

document.querySelector('.cache-article').addEventListener('click', function(event) {
  event.preventDefault();

  var id = this.dataset.articleId;
  // 建立 caches 套件
  caches.open('mysite-article-' + id).then(function(cache) {
    fetch('/get-article-urls?id=' + id).then(function(response) {
      // 返回 json 对象
      return response.json();
    }).then(function(data) {
    // 缓存指定路由
      cache.addAll(data);
    });
  });
});复制代码

这里我就不赘述了,简单来讲就是更新一下缓存。

Caches 相关

上面大体了解了一下关于 SW 的基本流程,不过说到底,SW 只是一个容器,它的内涵只是一个驻留后台进程。咱们想关心的是,在这进程里面,咱们能够作些什么?
最主要的应该有两个东西,缓存和推送。这里咱们主要讲解一下缓存。不过在SW 中,咱们通常只能缓存 POST
上面在文件更新里面也讲了几个更新的方式。简单来讲:

desk

简单的情形上面已经说了,我这里专门将一下比较复杂的内容。

网络缓存同时干

这种情形通常是用来装逼的,一方面检查请求,一方面有检查缓存,而后看两个谁快,就用谁,我这里直接上代码吧:

function promiseAny(promises) {
  return new Promise((resolve, reject) => {
    // 经过 promise 的 resolve 特性来决定谁快
    promises = promises.map(p => Promise.resolve(p));
    // 这里调用外层的 resolve
    promises.forEach(p => p.then(resolve));
    // 若是其中有一方出现 error,则直接挂掉
    promises.reduce((a, b) => a.catch(() => b))
      .catch(() => reject(Error("All failed")));
  });
};

self.addEventListener('fetch', function(event) {
  event.respondWith(
    promiseAny([
      caches.match(event.request),
      fetch(event.request)
    ])
  );
});复制代码

老是更新

这里就和咱们在后台配置的 Last-Modifier || Etag 同样,询问更新的文件内容,而后执行更新:

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;
      });
    })
  );
});复制代码

先返回后更新

这应该是目前为止最佳的体验,返回的时候不会影响正在发送的请求,而接受到的新的请求后,最新的文件会替换旧的文件。(这个就是前面写的代码):

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return cache.match(event.request).then(function(response) {
        var fetchPromise = fetch(event.request).then(function(networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        })
        return response || fetchPromise;
      })
    })
  );
});复制代码

接下来,咱们来详细了解一下关于 Cache Object 相关的内容。加深印象:

Cache Object

Cache 虽然是在 SW 中定义的,可是咱们也能够直接在 window 域下面直接使用它。它经过 Request/Response 流(就是 fetch)来进行内容的缓存。每一个域名能够有多个 Cache Object,具体咱们能够在控制台中查看:

cacheObject

而且 Cache Object 是懒更新,实际上,就能够把它比喻为一个文件夹。若是你不本身亲自更新,系统是不会帮你作任何事情的。对于删除也是同样的道理,若是你不显示删除,它会一直存在的。不过,浏览器对于每一个域名的 Cache Object 数量是有限制的,而且,会周期性的删掉一些缓存信息。最好的办法,是咱们本身管理资源,官方给出的建议是: 使用版本号进行资源管理。上面我也展现过,删除特定版本的缓存资源:

self.addEventListener('activate', function(event) {
  var cacheWhitelist = ['v2'];

  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(key);
        }
      }));
    })
  );
});复制代码

Cache Object 操做相关方法

这里,咱们就能够将 Cache Object 理解为一个持久性数据库,那么针对于数据库来讲,简单的操做就是 CRUD。而 Cache Object 也提供了这几个接口,而且接口结果都是经过 Promise 对象返回的,成功返回对应结果,失败则返回 undefined:

  • Cache.match(request, options): 成功时,返回对应的响应流--response。固然,查找的时候使用的是正则匹配,表示是否含有某个具体字段。
    • options:
      • ignoreSearch[boolean]:是否忽略 querystring 的查找。即,咱们查找的区域不包括 qs。好比: http://foo.com/?value=bar,咱们不会再搜索 ?value=bar 这几个字符。
      • ignoreMethod[boolean]:当设置为 true 时,会防止 Cache 验证 http method,默认状况下,只有 GET 和 HEAD 可以经过。默认值为 false。
      • ignoreVary[boolean]:当设置为 true 时,表示不对 vary 响应头作验证。即, Cache 只须要经过 URL 作匹配便可,不须要对响应头 vary 作验证。默认值为 false。
      • cacheName[String]: 本身设置的缓存名字。通常用不到,match 会自动忽略。
cache.match(request,{options}).then(function(response) {
  //do something with the response
});复制代码
  • Cache.matchAll(request, options): 成功时,返回一个数组,包含全部匹配到的响应流。options 和上面的同样,这里就很少说了。
cache.matchAll(request,{options}).then(function(response) {
    response.forEach(function(element, index, array) {
      cache.delete(element);
    });
});复制代码
  • Cache.add(url): 这实际上就是一个语法糖。fetch + put。即,它会自动的向路由发起请求,而后缓存获取到的内容。
cache.add(url).then(function() {
  // 请求的资源被成功缓存
});

# 等同于
fetch(url).then(function (response) {
  if (!response.ok) {
    throw new TypeError('bad response status');
  }
  return cache.put(url, response);
})
.then(res=>{
    // 成功缓存
})复制代码
  • Cache.addAll(requests):这个就是上面 cache.add 的 Promise.all 实现方式。接受一个 Urls 数组,而后发送请求,缓存上面全部的资源。
this.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('v1').then(function(cache) {
      return cache.addAll([
        '/public/',
        '/public/index.html',
        '/public/style.css',
        '/public/app.js'
      ]);
    })
  );
});复制代码
  • Cache.put(request, response): 将请求的资源以 req/res 键值对的形式进行缓存。若是,以前已经存在对应的 req(即,key 值),那么之前的值将会被新值覆盖。
cache.put(request, response).then(function() {
  // 成功缓存
});复制代码
  • Cache.delete(request, options): 用来删除指定的 cache。若是你不删除,该资源会永远存在(除非电脑自动清理)。
  • Cache.keys(request, options): 返回当前缓存资源的全部 key 值。
cache.keys().then(function(keys) {
    keys.forEach(function(request, index, array) {
      cache.delete(request);
    });
  });复制代码

能够查看到上面的参数都共同的用到了 request 这就是 fetch 套件里面的请求流,具体,能够参考一下前面的代码。上面全部方法都是返回一个 Promise 对象,用来进行异步操做。

上面简单介绍了一下 Cache Object,但实际上,Cache 的管理方式是两级管理。即,最外层是 Cache Storage,下一层是 Cache Object

Cache Storage

浏览器会给每一个域名预留一个 Cache Storage(只有一个)。而后,剩下的缓存资源,所有都存在下面。咱们能够理解为,这就是一个顶级缓存目录管理。而咱们获取 Cache Object 的惟一途径,就是经过 caches.open() 进行获取。这里,咱们就能够将 open 方法理解为 没有已经存在的 Cache Object 则新建,不然直接打开。它的相关操做方法也有不少:

  • CacheStorage.match(request,{options}):在全部的 Cache Object 中进行缓存匹配。返回值为 Promise
caches.match(event.request).then(function(resp) {
  return resp || fetch(event.request).then(function(r) {
    caches.open('v1').then(function(cache) {
      cache.put(event.request, r);
    });
    return r.clone();
  });
});复制代码
  • CacheStorage.has(cacheName): 用来检查是否存在指定的 Cache Object。返回 Boolean 表明是否存在。
caches.has('v1').then(function(hasCache) {
 // 检测是否存在 Cache Object Name 为 v1 的缓存内容
  if (!hasCache) {
    // 没存在
  } else {
    //...
  }
}).catch(function() {
  // 处理异常
});复制代码
  • CacheStorage.open(cacheName): 打开指定的 Cache Object。并返回 Cache Object。
caches.open('v1').then(function(cache) {
    cache.add('/index.html');
  });复制代码
  • CacheStorage.delete(cacheName): 用来删除指定的 Cache Object,返回值为 Boolean:
caches.delete(cacheName).then(function(isDeleted) {
  // 检测是否删除成功
});

# 经过,能够经过 Promise.all 的形式来删除多个 cache object
Promise.all(keyList.map(function(key) {
        if (cacheList.indexOf(key) === -1) {
          return caches.delete(keyList[i]);
        }
      });复制代码
  • CacheStorage.keys(): 以数组的形式,返回当前 Cache Storage 保存的全部 Cache Object Name。
event.waitUntil(
 caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (['v1','v2'].indexOf(key) === -1) {
          return caches.delete(keyList[i]);
        }
      });
    })
    );复制代码

上面就是关于 Cache Storage 的全部内容。

这里放一张本身写的总结图吧:

Service+Worker.svg-44kB

原文连接:ivweb.io/topic/5876d…

相关推荐
React 同构思想
Vue.js先后端同构方案之准备篇——代码优化
Vue组件开发实践之scopedSlot的传递


此文已由做者受权腾讯云技术社区发布,转载请注明文章出处
原文连接:www.qcloud.com/community/a…
获取更多腾讯海量技术实践干货,欢迎你们前往腾讯云技术社区

相关文章
相关标签/搜索