Service Worker 生命周期

原文链接: Service Worker 生命周期

上一篇: PWA Notification API来进行消息提醒 前后台通信

下一篇: Service Worker 生命周期 案例

【Service Worker】生命周期那些事儿

生命周期

eeb5ebd5ab415d5237ba9caece80e56dbd0.jpg

支持的事件

bb4257b8655d5f7ac0cef4a33df1b6136ca.jpg

  • install :Service Worker 安装成功后被触发的事件,在事件处理函数中可以添加需要缓存的文件(详见 使用 Service Worker

  • activate :当 Service Worker 安装完成后并进入激活状态,会触发 activate 事件。通过监听 activate 事件你可以做一些预处理,如对旧版本的更新、对无用缓存的清理等。(详见 更新 Service Worker

  • message :Service Worker 运行于独立 context 中,无法直接访问当前页面主线程的 DOM 等信息,但是通过 postMessage API,可以实现他们之间的消息传递,这样主线程就可以接受 Service Worker 的指令操作 DOM。

Service Worker 有几个重要的功能性的的事件,这些功能性的事件支撑和实现了 Service Worker 的特性。

  • fetch (请求) :当浏览器在当前指定的 scope 下发起请求时,会触发 fetch 事件,并得到传有 response 参数的回调函数,回调中就可以做各种代理缓存的事情了。

  • push (推送) :push 事件是为推送准备的。不过首先需要了解一下 Notification API PUSH API 。通过 PUSH API,当订阅了推送服务后,可以使用推送方式唤醒 Service Worker 以响应来自系统消息传递服务的消息,即使用户已经关闭了页面。

  • sync (后台同步) :sync 事件由 background sync (后台同步)发出。background sync 配合 Service Worker 推出的 API,用于为 Service Worker 提供一个可以实现注册和监听同步处理的方法。但它还不在 W3C Web API 标准中。在 Chrome 中这也只是一个实验性功能,需要访问 chrome://flags/#enable-experimental-web-platform-features ,开启该功能,然后重启生效。

第一个 Service Worker

  • install 事件是 SW 触发的第一个事件,并且仅触发一次。

  • installEvent.waitUntil() 接收一个 Promise 参数,用它来表示 SW 安装的成功与否。

  • SW 在安装成功并激活之前,不会响应 fetch push 等事件。

  • 默认情况下,页面的请求(fetch)不会通过 SW,除非它本身是通过 SW 获取的,也就是说,在安装 SW 之后,需要刷新页面才能有效果。

  • clients.claim() 可以改变这种默认行为。

作用域与控制

SW 的默认作用域为基于当前文件 URL 的 ./ 。意思就是如果你在 //example.com/foo/bar.js 里注册了一个 SW,那么它默认的作用域为 //example.com/foo/

我们把页面,workers,shared workers 叫做 clients 。SW 只能对作用域内的 clients 有效。一旦一个 client 被“控制”了,那么它的请求都会经过这个 SW。我们可以通过查看 navigator.serviceWorker.controller 是否为 null 来查看一个 client 是否被 SW 控制。

下载-解析-执行

当你调用 .register() 的时候,第一个 SW 被下载下来,这过程中如果下载,解析或者在初始化中有错误的话,那么 register 的Promise 会返回 reject,然后 SW 会被销毁。

安装(install)

SW 首先会触发 install ,每个 SW 只会被触发一次,当你修改你的 SW 后,浏览器会认为这是一个新的 SW,从而会再触发这个新 SW 的 install 事件,在后面会详细说到。

install 是在 SW 控制 clients 之前处理缓存很好的时机。在 event.waitUntil() 传入的 Promise 会让浏览器知道 SW 什么时候安装成功以及是否成功。

当 Promise reject 的时候,代表着安装失败,浏览器将这个 SW 废弃掉,不会控制任何 clients。

激活(Activate)

安装成功后并激活(activate)成功后,SW 就可以处理“功能性的事件“了,比如 push , sync 但这并不代表调用 .register() 的页面会立即生效。

第一次你请求 这个demo 的时候,虽然在 SW 被激活后很久才请求了 dog.svg (因为这里等待了三秒),但 SW 也并没有处理这个请求,结果你看见了一只狗。当你第二次请求的时候,也就是刷新页面,这时请求被处理了,当前页面和图片都经过了 SW 的 fetch 事件,所以你看见了一只猫。

clients.claim

你可以在 activate 事件中通过调用 clients.claim() 来让没被控制的 clients 受控。

比如 这个demo ,可能第一次你就会看见一只猫,这里我说“可能”,是因为这时时间敏感的,仅当 SW 激活并且 clients.claim() 被调用成功在图片请求之前的时候才可以。

所以,可想而知,当你用 SW 加载与正常请求不同资源的时候(比如上面的例子),那用 clients.claim() 可能会遇到一些问题,这时有些资源可能不会通过你的SW。

我见过很多人在代码中把 clients.claim() 当做了必选项,但我自己很少这样做,因为仅仅是第一次加载不会通过 SW,而且页面还是都会正常运行的。

更新 Service Worker

简单来说:

  • 触发更新的几种情况:

    • 第一次导航到作用域范围内页面的时候

    • 当在24小时内没有进行更新检测并且触发功能性时间如 push sync 的时候

    • SW 的 URL 发生变化并调用 .register()

  • 当 SW 代码发生变化,SW 会做更新(还将包括引入的脚本)

  • 更新后的 SW 会和原始的 SW 共同存在,并运行它的 install

  • 如果新的 SW 不是成功状态,比如 404,解析失败,执行中报错或者在 install 过程中被 reject,它将会被废弃,之前版本的 SW 还是激活状态不变。

  • 一旦新 SW 安装成功,它会进入 wait 状态直到原始 SW 不控制任何 clients。

  • self.skipWaiting() 可以阻止等待,让新 SW 安装成功后立即激活。

install

注意这里我们将缓存从 static-v1 换到了 static-v2,这代表了我用了一个新的缓存空间覆盖了之前 SW 正在使用的缓存。

这里新建了一块缓存的做法类似于原生 app 中将每块资源打包到一块指定的执行空间的做法,有时候结合实际情况,你也可以不这么做。

Waiting

一旦新 SW 安装成功,它会进入 wait 状态直到原始 SW 不控制任何 clients。这个状态是 waiting ,这也是浏览器确保在同一时间只有一个版本的 SW 运行的机制。

如果你再次打开 这个 demo ,你还是会看到一只喵,因为新的 SW 还是没有被激活,在开发者工具里你依然看到它是 waiting 状态。

尽管这个例子中你仅打开了一个 tab,但刷新页面并没有用,这是由于浏览器本身的机制,当你刷新的时候,当前页面不会离开,直到收到了一个响应头,而且即使这样,如果响应中包含 Content-Disposition 的话,当前页面还是不会离开。由于这个时间上的重叠,在刷新的时候当前的 SW 总是控制了一个 client。

为了让 SW 更新,你需要把所有用原始 SW 的页面 tab 关闭或者跳转走,这时你再访问 这个 demo ,你就会看到了一匹野马。

这种机制类似于 Chrome 本身的更新机制,Chrome 在后台更新,只有当你重启浏览器的时候才会生效,在这期间你不会被打扰,可以继续使用当前版本。然而,这样可能会使我们开发者比较痛苦,好在开发者工具帮我们解决了这个事情,后面会说到。

Activate

Activate 在旧的 SW 离开时会被触发,这时新的 SW 可以控制 clients。这时候你可以做一些在老 SW 运行时不能做的事情,比如清理缓存。

在上面的例子中,之前保留的缓存,在 activate 时间执行的时候被清理掉。

这里最好不要更新以前的版本,而是直接分配新的缓存空间。

如果你在 event.waitUntil() 中传入了一个 Promise,SW 将会缓存住功能性事件( fetch , push , sync 等等),直到 Promise 返回 resolve 的时候再触发,也就是说,当你的 fetch 事件被触发的时候,SW 已经被完全激活了。

cache storage API 和 localStorage,IndexedDB 一样是“同域”的。如果你在一个父域下运行多个网站,比如 yourname.github.io/myapp ,这就要小心你不要把别的网站的缓存删掉了。避免这个问题,你可以将 cache 的 key 设的具有唯一性,比如 myapp-static-v1 并且约束不要碰不以 myapp- 开头的缓存。

skipWaiting

waiting 意在让你的网站同一时间只有一个 SW 在运行,但如果你不想要这样的话,你可以通过调用 self.skipWaiting() 来让新 SW 立即激活。

这么做会让你的新 SW 踢掉旧的,然后当它变为 waiting 状态时立即激活,注意这里不会跳过 installing,只会跳过 waiting。

在 waiting 之前或者之后调用 skipWaiting() 都可以,一般情况我们在 install 事件中调用:

self.addEventListener('install', event => {
  self.skipWaiting();

  event.waitUntil(
    // caching etc
  );
});

这个例子 中,你可能直接可以看到一只奶牛,和 clients.claim() 一样,这是一场赛跑,仅当你的新 SW 安装,激活等早于你请求图片时,奶牛才会出现。

skipWaiting() 意味着新 SW 控制了之前用旧 SW 获取的页面,也就是说你的页面有一部分资源是通过旧 SW 获取,剩下一部分是通过新 SW 获取的,如果这样做会给你带来麻烦,那就不要用 skipWaiting() ,这点我们应该根据具体情况评估。

手动更新

像我之前说的,当页面刷新或者执行功能性事件时,浏览器会自动检查更新,其实我们也可以手动的来触发更新:

navigator.serviceWorker.register('/sw.js').then(reg => {
  // sometime later…
  reg.update();
});

如果你希望你的用户访问页面很长时间而且不用刷新,那么你可以每个一段时间调用一次 update()

避免改变 SW 的 URL

如果你看过我的文章 缓存最佳实践 ,你可能会考虑给每个 SW 不同的 URL。 千万不要这么做! 在 SW 中这么做是“最差实践”,要在原地址上修改 SW。

举个例子来说明为什么:

1. index.html 注册了 sw-v1.js 作为SW。

2. sw-v1.js index.html 做了缓存,也就是缓存优先(offline-first)。

3.你更新了 index.html 重新注册了在新地址的 SW sw-v2.js .

如果你像上面那么做,用户永远也拿不到 sw-v2.js ,因为 index.html sw-v1.js 缓存中,这样的话,如果你想更新为 sw-v2.js ,还需要更改原来的 sw-v1.js

在上面的 demo 里,我给每个 SW 用了不同的 URL,这只是为了做演示,不要在生产环境中这么做。

让开发更简单

SW 的生命周期是为了用户构建的,但这样难免让我们开发带来一些烦恼,幸亏与一些工具来帮助我们。

Update on reload

e6993c50d670390b74a40bc5dfa96711147.jpg

这样把生命周期变得对开发友好了,每次跳转将会:

1.重新获取 SW

2.尽管字节一致,也会重新安装,也就是说 install 事件被执行并且更新缓存。

3.跳过 waiting,激活新的 SW。

4.导航到这个页面。

这就是说你每次操作都会更新而不用刷新页面或者关闭 tab。

// 浏览器没有让当前已经注册的所有service worker一直保持运行状态
// sw的生命周期直接与他所处理的事件的执行联系在一起
// 一旦处理完来自页面的事件,他就终止了,如果稍后发生了另一个事件,将会再次启动
// 如下方法
// 当fetch去异步请求数据时,事件监听器已经停止执行,一旦事件结束,在响应返回之前
// sw就会被浏览器终止,导致无法显示通知
self.addEventListener("push", function(event) {
  fetch(event.url).then(response =>
    self.registration.showNotification(response.text())
  );
});

// sw的生命周期和他所处理的事件执行直接相关
// 使用waitUntil拓展事件的执行
// 告知push事件等待我们传入的一个promise完成或者失败,才能认为事件已经完成
// 意味着sw的生命周期也得到了延长

self.addEventListener("push", function(event) {
  event.waitUntil(
    fetch(event.url).then(response =>
      self.registration.showNotification(response.text())
    )
  );
});

Service Worker 是一种在浏览器后台运行的脚本,它可以独立于网页运行,并提供了强大的功能,如离线缓存、推送通知和后台同步等。 Service Worker 作为浏览器中的中间层,可以拦截和处理网络请求,以实现离线缓存和缓存策略控制。它可以在用户离线时从缓存中提供响应,从而使网页能够在离线状态下继续加载和展示内容。当用户重新连接到网络时,Service Worker 可以自动将离线期间的请求发送到服务器,并更新缓存。 除了离线缓存,Service Worker 还可以提供推送通知功能,使网站能够向用户发送实时通知,即使用户当前不在网站上。这使得网站可以及时地向用户推送重要的消息和更新。 此外,Service Worker 还支持后台同步功能,即使用户关闭了网页,也可以在后台进行网络请求和数据同步。这使得网站能够处理一些关键操作,如后台数据同步、离线提交等。 Service Worker 是基于事件驱动的,它可以通过监听各种事件,如 `install`、`activate`、`fetch` 等来执行相应的操作。通过这些事件生命周期方法,开发者可以控制 Service Worker生命周期,并实现自定义的缓存策略和网络请求处理逻辑。 需要注意的是,由于 Service Worker 运行在浏览器的后台,因此它具有一些安全限制,如只能在 HTTPS 协议下使用,并且有一些基于用户授权的限制。 总之,Service Worker 是一种强大的浏览器技术,可以提供离线缓存、推送通知和后台同步等功能,为网页带来更好的性能和用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值