service worker 介绍

service worker 是运行在浏览器后台的 JS 程序,可以操控浏览器端的存储、网络请求、消息推送等。特点:

  • 基于 web worker(一个独立于JS主线程的线程,在里面执行耗时操作不会堵塞主线程)
  • 在 web worker 的基础上增加了离线缓存的能力;
  • 本质上充当服务器与浏览器之间的代理服务器,可以拦截全站的请求并响应;
  • 由事件驱动,具有生命周期,包含 register / install / activate 等阶段;
  • 只能在 https 域 或者 localhost 下使用;
  • 支持降级处理,客户端不支持 service worker 时,对流程没有任何影响;

1. 使用步骤

把 service worker 看作一个代理服务器,通过例子直观的感受它。

1.1 注册 service worker 任务

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function () {
    navigator.serviceWorker.register('sw.js').then(function (registration) {
      console.log('ServiceWorker registration successful with scope: ', registration.scope);
    }, function (err) {
      console.log('ServiceWorker registration failed: ', err);
    });
  });
}

1.2 处理安装逻辑

// 监听安装事件
// install 事件一般是被用来设置你的浏览器的离线缓存逻辑
this.addEventListener("install", function (event) {
  // event.waitUntil(Promise) 保证缓存动作完成后,才会进入 installed 状态
  // 该方法会等待 Promise resolved
  event.waitUntil(
    // 打开 key 为 pwa-static 的缓存
    caches.open("pwa-static").then(function (cache) {
            // cache.addAll 指定要缓存的内容,地址为相对于跟域名的访问路径
      return cache.addAll(["./index.html"]);
    })
  );
});

监听 install 事件,当 install 事件触发时,进入 installing 阶段,使用 event.waitUntil() 保证 index.html 被缓存之后才进入 installed 状态。caches.open 用于打开一个缓存,cache.addAll 用于添加需要缓存的文件,参数为url列表。如果 event.waitUntil 接收的 promise 发生错误,则会导致 install 失败。比如添加一个不存在的文件地址。

1.3 监听 fetch 事件

// 注册fetch事件, 拦截请求, 可以拦截所有请求
this.addEventListener('fetch', function(event) {
  // 在缓存中匹配对应请求资源直接返回
  event.respondWith(caches.match(event.request));
});

在 service worker 中可以监听任何 http 请求,定义响应策略,上面的代码中表示从缓存中读取并响应。

1.4 一个完整的例子

// register service worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw-test/sw.js', { scope: '/sw-test/' }).then(function(reg) {

    if(reg.installing) {
      console.log('Service worker installing');
    } else if(reg.waiting) {
      console.log('Service worker installed');
    } else if(reg.active) {
      console.log('Service worker active');
    }

  }).catch(function(error) {
    // registration failed
    console.log('Registration failed with ' + error);
  });
}
const version = 'v7';

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(version).then(function(cache) {
      return cache.addAll([
        // '/sw-test/',
        // '/sw-test/index.html',
        '/sw-test/style.css',
        '/sw-test/app.js',
        '/sw-test/image-list.js',
        '/sw-test/star-wars-logo.jpg',
        '/sw-test/gallery/bountyHunters.jpg',
        '/sw-test/gallery/myLittleVader.jpg',
        '/sw-test/gallery/snowTroopers.jpg'
      ]);
    }).then(() => {
      return self.skipWaiting()
    })
  );
});

// 清理过期缓存
function clearCache() {
  console.log("clearCacheclearCacheclearCacheclearCache")
  return caches.keys().then((keys) => {
    return Promise.all(
      keys.map((key) => {
        console.log(key, version)
        if (key !== version) {
          return caches.delete(key);
        }
      })
    );
  });
}

// 激活事件
self.addEventListener("activate", (event) => {
  console.log("activate");
  const list = Promise.all([clearCache(), self.clients.claim()])
  event.waitUntil(list);
});

self.addEventListener('fetch', function(event) {
  event.respondWith(caches.match(event.request).then(function(response) {
    if (response !== undefined) {
      return response;
    } else {
      return fetch(event.request).then(function (response) {
        let responseClone = response.clone();
        return caches.open(version).then(function (cache) {
          cache.put(event.request, responseClone);
          return response;
        })
      }).catch(function (err) {
        console.log(err, 'err')
        return caches.match('/sw-test/gallery/myLittleVader.jpg');
      });
    }
  }));
});

2. API 介绍

2.1 Cache API

service worker 中 self.caches 暴露了访问 cache 的能力,详情见 https://developer.mozilla.org/zh-CN/docs/Web/API/Cache

// service-worker.js
// self.caches
// self.caches.open(key: string)
// caches.keys()
// caches.delete(key: string);
Cache.match(request, options)
// 返回一个 Promise对象,resolve的结果是跟 Cache 对象匹配的第一个已经缓存的请求,找不到返回 undefined;

Cache.add(request)
// 抓取这个URL, 检索并把返回的 response 对象添加到给定的 Cache 对象。这在功能上等同于调用 fetch(), 然后使用 Cache.put() 将 response 添加到 cache 中;

Cache.addAll(requests)
// 抓取一个URL数组,检索并把返回的response对象添加到给定的Cache对象;

Cache.put(request, response)
// 同时抓取一个请求及其响应,并将其添加到给定的cache。

Cache.delete(request, options)
// 搜索key值为request的Cache 条目。如果找到,则删除该Cache 条目,并且返回一个resolve为true的Promise对象;如果未找到,则返回一个resolve为false的Promise对象。

Cache.keys(request, options)
// 返回一个Promise对象,resolve的结果是Cache对象key值组成的数组。

2.2 event.waitUntil()

查看详细文档,这个方法告诉浏览器事件一直进行,直至 promise 解决,浏览器不应该在事件中的异步操作完成之前终止服务工作线程。

  • install 事件使用 waitUntil() 来将服务工作线程保持在 installing 阶段。如果传入 waitUntil() 的 promise 被拒绝,则将此次安装视为失败,丢弃这个服务工作线程。这主要用于确保在服务工作线程安装以前,所有依赖的核心缓存都已经成功载入。
  • activate 事件使用 waitUntil() 来延迟函数事件,如 fetch 和 push,直至传入 waitUntil() 的 promise 被解决。这让服务工作线程有时间更新数据库和删除过时缓存,让其他事件能在一个完成更新的状态下进行。

2.3 self.skipWaiting()

当安装成功后,更新的 Worker会处于等待状态,直到现有 Worker 让出授权,另外在刷新期间客户端会重叠。
使用 self.skipWaiting() 会跳过等待,Service Worker 在安装完后立即激活。

3. 注意事项

  • 非 PWA 应用,客户端不要缓存 html 文件,否则更会非常的麻烦;
  • service worker 文件本身的更新,依赖 HTTP 缓存策略;
  • service worker 文件本身,服务端最好设置 cache-control: no-store 不要缓存;
  • 多次 register() 同一个 Service Worker 不会触发更新

4. 参考文档

  1. 使用 Service Workers [ https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API/Using_Service_Workers#service_workers_demo]
  2. Service Worker: 简介 [https://developers.google.cn/web/fundamentals/primers/service-workers/]
  3. MDN Cache [https://developer.mozilla.org/zh-CN/docs/Web/API/Cache]
  4. MDN fetch [https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch]
  5. Service Worker API [https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API#service_worker_%E7%9A%84%E6%A6%82%E5%BF%B5%E5%92%8C%E7%94%A8%E6%B3%95]
  6. sw-test [https://github.com/mdn/sw-test]
  7. PWA 入门: 理解和创建 Service Worker 脚本 [https://zhuanlan.zhihu.com/p/25524382]
  8. SW 更新 https://harttle.land/2017/04/10/service-worker-update.html#header-0
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容