前端工程化->使用Service Worker和离线缓存Cache(转载)

今天在用create-react-app构建项目的时候发现多了一个serviceWorker做离线缓存的选项。

(1)什么是Service Worker

Service Worker是一个注册在指定源和路径下的事件驱动 worker。它采用JavaScript控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是离线)下的表现。

Service Worker运行在worker上下文,因此它不能访问DOM。相对于驱动应用的主JavaScript线程,它运行在其他线程中,所以不会造成阻塞。它设计为完全异步,同步API(如XHR和localStorage)不能在service worker中使用。

出于安全考量,Service workers只能由HTTPS承载,毕竟修改网络请求的能力暴露给中间人攻击会非常危险。

为什么不直接用web worker?

Web Worker 是临时的,每次做的事情的结果还不能被持久存下来,如果下次有同样的复杂操作,还得费时间的重新来一遍。那我们能不能有一个Worker 是一直持久存在的,并且随时准备接受主线程的命令。基于这样的需求推出了最初版本的 Service Worker ,Service Worker 在 Web Worker 的基础上加上了持久离线缓存能力。

在 Service Worker 之前也有一个在 HTML5 上做离线缓存的 API 叫 AppCache, 但是 AppCache 就是一坨shit(别人说的)。W3C 决定 AppCache 仍然保留在 HTML 5.0 Recommendation 中,在 HTML 后续版本中移除。

(2)使用Service Worker

上面说到 Service Worker 必须使用https协议,但本地开发弄个https协议是很麻烦的。好在还算人性化,Service Worker在http://localhost或者http://127.0.0.1这种本地环境下的时候是可以跑起来的。

这里我推荐一个经常使用的npm工具,用来开启本地服务:live-server。

1.注册worker

就像使用web worker那样,先创建两个文件serviceWorker.html、serviceWorker.js。

serviceWorker.html:

<script type="text/javascript">

    if ('serviceWorker' in navigator) {
        window.addEventListener('load', function () {
            navigator.serviceWorker.register('./serviceWorker.js', {scope: './'})
                .then(function (registration) {

                    console.log('ServiceWorker registration successful with scope: ', registration.scope);
                })
                .catch(function (err) {

                    console.log('ServiceWorker registration failed: ', err);
                });
        });
    }

</script>

  1. 首先在注册之前确保浏览器是支持 service worker 的。
  2. 接着,我们使用 ServiceWorkerContainer.register() 函数来注册serviceWorker.js。
  3. scope 参数是可选的,可以用来指定你想让 service worker 控制的内容的子目录。 在这个例子里,我们指定了 '/',表示 根网域下的所有内容。这也是默认值。
  4. 函数返回一个Promise,注册成功时执行.then(),失败时执行.catch()。
2.安装worker

serviceWorker.js:

this.addEventListener('install', function (event) {

    event.waitUntil(
        caches.open('v1').then(function (cache) {
            return cache.addAll([
                './index.html'
            ]);
        })
    );
});

install 事件会在worker安装完成后触发。install 事件一般是被用来设置你的浏览器的离线缓存能力。这里就用到了离线缓存Cache了。

  1. 这里我们 新增了一个 install 事件监听器,接着event对象调用了ExtendableEvent.waitUntil() 方法——这会确保Service Worker 不会在 waitUntil() 里面的代码执行完毕之前就安装完成。(这个方法扩展了事件的生命周期。在service worker线程中,延长事件的寿命从而阻止浏览器在事件中的异步操作完成之前终止service worker线程。)

  2. 在 waitUntil() 里,我们使用了caches.open() 方法来创建了一个叫做 v1 的新的缓存,这是我们的站点资源缓存的第一个版本。它返回了一个创建缓存的 promise。当它 resolved的时候,我们接着会调用创建的缓存上的 addAll() 方法 ,这个方法的参数是一个数组,每个元素是相对于根域的 URL,这些 URL 就是你要缓存的资源的路径。(这里我就缓存了一个index.html)

  3. 如果 promise 被 rejected,安装就会失败,这个 worker 不会做任何事情。

当启动服务运行后,可以看到:

image

打开缓存,也可以看到index.html被缓存了:

image

(3)fetch API

现在已经将index.html缓存了,你要告诉 service worker 怎么用这些缓存。这就要用到 fetch 事件。

给 service worker 添加一个 fetch 的事件监听器,接着调用 event 上的 respondWith() 方法来劫持我们的 HTTP 请求,然后你用可以用自己的方式来更新。

this.addEventListener('fetch', function(event) {
  event.respondWith(
    // magic goes here
  );
});

这里最简单的做法就是,原原本本的返回与url匹配的缓存资源。

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

caches.match(event.request)允许我们将网络请求的资源和 cache 里可获取的资源进行匹配,查看是否缓存中有相应的资源。这个匹配通过 url 和各种header进行,就像正常的 http 请求一样。

原来的翻译多了个;号,难怪我复制后会报错,现在已经被我改过来了。

现在,就可以打开控制台断开你的网络,离线。再次访问index.html。
可以发现还是ok的。

上面是最简单的例子,当缓存中没有匹配的资源时,或当缓存中没有匹配的资源,同时网络也不可用时。更多详情就直接看MDN吧。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容