来自 「何天翔」 同学的内部分享。
Service Workers 本质上是一种能在浏览器后台运行的独立线程,它能够在网页关闭后持续运行,能够拦截网络请求并根据网络是否可用来采取适当的动作、更新来自服务器的的资源,从而实现拦截和加工网络请求、消息推送、静默更新、事件同步等一系列功能,是 PWA 应用的核心技术之一。
与普通 JS 运行环境相比,Service Workers 有如下特点:
本文将从应用角度,简单汇总下 Service Workers 几个核心概念,包括:API、生命周期、waitUntil 机制、调试等。
Service Worker 的生命周期完全独立于网页。生命周期 (install -> waiting -> activate -> fetch):
其中, install
事件是 Service Worker 获取的第一个事件,并且只发生一次。
// 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)
})
参考:https://developer.mozilla.org/zh-CN/docs/Web/API/ExtendableEvent/waitUntil
ExtendableEvent.waitUntil()
方法告诉事件分发器该事件仍在进行。这个方法也可以用于检测进行的任务是否成功。在服务工作线程中,这个方法告诉浏览器事件一直进行,直至 promise resolve,浏览器不应该在事件中的异步操作完成之前终止服务工作线程。
Service Worker 一旦更新,需要等所有的终端都关闭之后,再重新打开页面才能激活新的 Service Worker,这个过程太复杂了。通常情况下,开发者希望当 Service Worker 一检测到更新就直接激活新的 Service Worker。如果不想等所有的终端都关闭再打开的话,只能通过 skipWaiting 的方法了。
Service Worker 在全局提供了一个 skipWaiting()
方法,skipWaiting()
在 waiting 期间调用还是在之前调用并没有什么不同。一般情况下是在 install 事件中调用它。
如果使用了 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
})
)
})
swUrl
发起请求,获取内容和和已有的 SW 比较。如没有差别,则结束安装。如有差别,则安装新版本的 SW(执行 install
阶段),之后令其等待(进入 waiting
阶段)activated
阶段),使之接管页面。问题:同一个页面,前半部分的请求是由 sw.v1.js
控制,而后半部分是由 sw.v2.js
控制。这两者的不一致性很容易导致问题,甚至网页报错崩溃
let refreshing = false
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) {
return
}
refreshing = true;
window.location.reload();
});
问题:毫无征兆的刷新页面的确不可接受,影响用户体验
大致的流程是:
updatefound
事件skipWaiting
并取得控制权controllerchange
事件,我们在这个事件的回调中刷新页面即可问题:
为了更熟练的运用 Chrome Devtools 调试 Service Worker,首先需要熟悉以下这些选项:
update on reload
复选框,接下来会注意到每次页面加载时此数字都会增大。在状态旁边会看到 start
按钮(如果 Service Worker 线程已停止)或 stop
按钮(如果 Service Worker 线程正在运行)。Service Worker 线程设计为可由浏览器随时停止和启动。使用 stop 按钮明确停止 Service Worker 线程可以模拟这一点。停止 Service Worker 线程是测试 Service Worker 线程再次重新启动时的代码行为方式的绝佳方法。它通常可以揭示由于对持续全局状态的不完善假设而引发的错误。show all
复选框,focus
按钮将非常实用。在此复选框启用时,系统会列出所有注册的 Service Worker 线程。如果这时候点击正在不同标签中运行的 Service Worker 线程旁的 focus
按钮,Chrome 会聚焦到该标签。基于service worker 可以实现拦截和处理网络请求、消息推送、静默更新、事件同步等服务。
参考链接: