[科普] Service Worker 入门指南

437次阅读  |  发布于2年以前

Service Worker 简介

Service Workers 本质上是一种能在浏览器后台运行的独立线程,它能够在网页关闭后持续运行,能够拦截网络请求并根据网络是否可用来采取适当的动作、更新来自服务器的的资源,从而实现拦截和加工网络请求、消息推送、静默更新、事件同步等一系列功能,是 PWA 应用的核心技术之一。

与普通 JS 运行环境相比,Service Workers 有如下特点:

本文将从应用角度,简单汇总下 Service Workers 几个核心概念,包括:API、生命周期、waitUntil 机制、调试等。

生命周期

Service Worker 的生命周期完全独立于网页。生命周期 (install -> waiting -> activate -> fetch):

其中, install 事件是 Service Worker 获取的第一个事件,并且只发生一次。

主要逻辑 & API

  // register
  if ('serviceWorker' in navigator) {
        // 由于 127.0.0.1:8000 是所有测试 Demo 的 host
        // 为了防止作用域污染,将安装前注销所有已生效的 Service Worker
        navigator.serviceWorker.getRegistrations()
          .then(regs => {
            for (let reg of regs) {
              reg.unregister()
            }
            navigator.serviceWorker.register('./sw.js')
          })
      }
// sw.js
console.log('service worker 注册成功')

self.addEventListener('install', () => {
  // 安装回调的逻辑处理
  console.log('service worker 安装成功')
})

self.addEventListener('activate', () => {
  // 激活回调的逻辑处理
  console.log('service worker 激活成功')
})

self.addEventListener('fetch', event => {
  console.log('service worker 抓取请求成功: ' + event.request.url)
})

「waitUntil 机制」

参考:https://developer.mozilla.org/zh-CN/docs/Web/API/ExtendableEvent/waitUntil

ExtendableEvent.waitUntil() 方法告诉事件分发器该事件仍在进行。这个方法也可以用于检测进行的任务是否成功。在服务工作线程中,这个方法告诉浏览器事件一直进行,直至 promise resolve,浏览器不应该在事件中的异步操作完成之前终止服务工作线程。

「skipWaiting」

Service Worker 一旦更新,需要等所有的终端都关闭之后,再重新打开页面才能激活新的 Service Worker,这个过程太复杂了。通常情况下,开发者希望当 Service Worker 一检测到更新就直接激活新的 Service Worker。如果不想等所有的终端都关闭再打开的话,只能通过 skipWaiting 的方法了。

Service Worker 在全局提供了一个 skipWaiting() 方法,skipWaiting() 在 waiting 期间调用还是在之前调用并没有什么不同。一般情况下是在 install 事件中调用它。

「clients.claim() 方法」

如果使用了 skipWaiting 的方式跳过 waiting 状态,直接激活了 Service Worker,可能会出现其他终端还没有受当前终端激活的 Service Worker 控制的情况,切回其他终端之后,Service Worker 控制页面的效果可能不符合预期,尤其是如果 Service Worker 需要动态拦截第三方请求的时候。

为了保证 Service Worker 激活之后能够马上作用于所有的终端,通常在激活 Service Worker 后,通过在其中调用 self.clients.claim() 方法控制未受控制的客户端。self.clients.claim() 方法返回一个 Promise,可以直接在 waitUntil() 方法中调用,如下代码所示:

self.addEventListener('activate', event => {
  event.waitUntil(
    self.clients.claim()
      .then(() => {
        // 返回处理缓存更新的相关事情的 Promise
      })
  )
})

如何处理 Service Worker 的更新

方法一:skipWaiting

问题:同一个页面,前半部分的请求是由 sw.v1.js 控制,而后半部分是由 sw.v2.js 控制。这两者的不一致性很容易导致问题,甚至网页报错崩溃

方法二:skipWaiting + 刷新

let refreshing = false
navigator.serviceWorker.addEventListener('controllerchange', () => {
  if (refreshing) {
    return
  }
  refreshing = true;
  window.location.reload();
});

问题:毫无征兆的刷新页面的确不可接受,影响用户体验

方法三:给用户一个提示

大致的流程是:

  1. 浏览器检测到存在新的(不同的)SW 时,安装并让它等待,同时触发 updatefound 事件
  2. 我们监听事件,弹出一个提示条,询问用户是不是要更新 SW
  3. 如果用户确认,则向处在等待的 SW 发送消息,要求其执行 skipWaiting 并取得控制权
  4. 因为 SW 的变化触发 controllerchange 事件,我们在这个事件的回调中刷新页面即可

问题:

如何调试

为了更熟练的运用 Chrome Devtools 调试 Service Worker,首先需要熟悉以下这些选项:

总结

完整流程

应用场景

基于service worker 可以实现拦截和处理网络请求、消息推送、静默更新、事件同步等服务。

  1. 离线缓存:配合 CacheStorage 可以将应用中不变化的资源或者很少变化的资源长久的存储在用户端,提升加载速度、降低流量消耗、降低服务器压力,提高请求速度,让用户体验更加丝滑
  2. 消息推送:激活沉睡的用户,推送即时消息、公告通知,激发更新等。如web资讯客户端、web即时通讯工具、h5游戏等运营产品。
  3. 事件同步:确保web端产生的任务即使在用户关闭了web页面也可以顺利完成。如web邮件客户端、web即时通讯工具等。
  4. 定时同步:周期性的触发Service Worker脚本中的定时同步事件,可借助它提前刷新缓存内容
  5. 结合CacheStorage、 Push API 和 Notification API

参考链接:

  • https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
  • https://juejin.cn/post/6844903792522035208
  • https://www.simicart.com/blog/pwa-service-worker/

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8