浅尝 service worker

最早听说service worker的时候是团队的分享会上,之后就听说了饿了么pwa改造,技术学习的脚步始终不能停。

对于一个新技术,它能做什么?它能给我带来什么?这一直是我在学习之前会反问的问题。

Service workers 本质上充当Web应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步API。

普通的浏览器环境下我们一般用它来缓存资源,提升页面打开速度。其他使用场景如下:

  • 后台数据同步
  • 响应来自其它源的资源请求
  • 集中接收计算成本高的数据更新,比如地理位置和陀螺仪信息,这样多个页面就可以利用同一组数据
  • 在客户端进行CoffeeScript,LESS,CJS/AMD等模块编译和依赖管理(用于开发目的)
  • 后台服务钩子
  • 自定义模板用于特定URL模式
  • 性能增强,比如预取用户可能需要的资源,比如相册中的后面数张图片

第四点在 React的开发配置项里就有体现,利用官方的create-react-app就会有一个service worker 文件。

出于安全原因,Service Workers 要求要在必须在 HTTPS 下才能运行。为了便于本地开发,localhost 也被浏览器认为是安全源。

在 Firefox 的 private browsing mode 隐私模式下是无法使用 service worker 的,但是chrome 的隐私模式可以使用。

注意: localStorage 跟  service worker 的 cache 工作原理很类似,但是它是同步的,所以不允许在  service workers 内使用。

注意: IndexedDB 可以在  service worker 内做数据存储。

var VERSION = 'v1';

//开始缓存
self.addEventListener('install',function(event) {
    event.waitUntil(
        caches.open(VERSION).then(function(cache) {
            return cache.addAll([

            ]);
        })
    );
});

任何被 service worker 控制的资源被请求到时,都会触发 fetch 事件,这些资源包括了指定的 scope 内的文档,和这些文档内引用的其他任何资源(比如 index.html 发起了一个跨域的请求来嵌入一个图片,这个也会通过 service worker 。)

this.addEventListener('fetch', function(event) {
  event.respondWith( //劫持请求做出相应
    caches.match(event.request); //符合缓存规则的请求
  );
});

由于生产环境需要考虑缓存没命中的情况下,需要向服务器获取资源。所以下面是升级版。

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

如果 promise reject了, catch() 函数会执行默认的网络请求,意味着在网络可用的时候可以直接像服务器请求资源。

接下来再进一步,把后续请求的资源也都缓存下来。

// 捕获请求并返回缓存数据
self.addEventListener('fetch', function(event) {
  event.respondWith(caches.match(event.request).catch(function() {
    return fetch(event.request);
  }).then(function(response) {
    caches.open(VERSION).then(function(cache) {
      cache.put(event.request, response);
    });
    return response.clone();
  }).catch(function() {
    return caches.match('./static/mm1.jpg');
  }));
});

下一步,缓存需要被更新了。

// 缓存更新
self.addEventListener('activate', function(event) {
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          // 如果当前版本和缓存版本不一致
          if (cacheName !== VERSION) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});