JS-service worker生命周期
前言
在上篇文章(service worker有关的对象)中,我们从方法、属性、事件、作用、地位等方面,详细地讲了与service worker有关的对象。这篇文章讲一下service worker线程的生命周期,看完这篇文章,会对service脚本的逻辑有个更整体的理解
上篇文章里讲到,serviceWorker对象有一个属性--state,它有5个值,分别是:installing、install、activating、activated、redundant。5个值分别代表service worker线程的5种状态。但是service worker线程还有一种状态parsed(已解析)。parsed状态是service worker线程最开始的状态。
是不是有点出乎你的想法。下面我们一起来看看
已解析(Parsed)状态
这个状态是service worker线程最开始的状态,比installing还要早。顾名思义,解析状态就是解析service脚本时候的状态,只有解析完成,才会开始安装。
那什么时候会解析service脚本呢?
在执行navigator.serviceWorker.register('./sw.js')
方法的时候,会先解析脚本,然后执行脚本。这和一般的js脚本的逻辑一致,都是先解析后执行。
但令人疑惑的是,为什么我要把这个状态单独领出来说,当然不仅仅service worker规范里提到了这个状态,而是这个状态有几件很重要的事情,需要被完成。
初始化任务
- 确保服务脚本来自相同的源。这个是处于安全的考量,避免加载不安全的脚本
什么是相同的源?脚本的URL,协议相同、路径相同、端口号相同
- 确保在安全的上下文中注册service worker线程。这个应该是需要确保通信协议是https,或者IP地址为localhost、127.0.0.1的http协议
- 确保service脚本可以被正确解析,不会出现语法错误
- 捕获服务脚本的快照,当下一次执行
register()
方法,拿到新脚本的时候,会和这个脚本快照进行比较,如果有差异,安装新脚本;否则什么也不做。
这就是前几篇文章中多次强调的,多次注册相同脚本,只会在第一次的时候生效,其他的不会执行任何操作。
如果上面的初始化任务都已经完成了,那么register()
方法返回的Promise就会解决为一个ServiceWorkerRegistration
对象,并且service worker线程进入installing状态
这两个时机,通过我多次的实践确认,后者比前者要早些。
还有一个有意思的点是,
ServiceWorker.state
没有parsed这样一个值,我猜是因为开发者无法介入该状态做任何事情。
安装中(installing)状态
刚进入这个状态,会触发service worker线程全局上下文的install事件,以及ServiceWorkerRegistration
对象的omupdatefound事件
这个阶段我们是可以传回调函数来监测的,而浏览器除了执行安装所需的任务,并不会做其他的事情。
按照最佳实践,我们一般在这个状态缓存自己所需要的缓存的所有资源
const cacheURLs = [
'./index.html',
'./index.css',
'./img/capture.PNG'
]
function preCache(){
retunr caches.open('v1')
.then(cache=>{
return cache.addAll(cacheURLs)
})
}
self.addEventListener('install', function(event){
event.waitUntil(
Promise.all(
[
preCache(),
self.skipWaiting()
]
)
)
})
上面的代码,就是一个在install事件的回调函数中缓存所需要的静态资源。
- 有个需要缓存资源的URL列表,把需要缓存的资源的URL填入进去就可以了
event.waitUntil()
,接收一个待解决的promise,该方法会等到传入的promise状态解决,才执行完成。作用就是将service worker线程的状态延迟到promise解决,才进入下一阶段。也就是等到所有资源缓存完成才进入下一阶段- 其中也调用了
self.skipWaiting()
,它的作用是使新版本的线程跳过等待状态,直接替换旧版本的线程 - 如果有任何资源缓存失败,都会让service worker线程状态直接变成失效态
已安装(installed/waiting)状态
这个状态啥也不做,就是等,等旧线程让位。也没有对应的事件。
要想跳过这个阶段进入下一阶段,除了self.skipWaiting()
,还有其他的办法吗?有的
当旧线程控制的客户端数量变成0了,即与线程相关的标签页全部都关闭了,浏览器就会把旧线程给失效掉。并在下一个导航事件触发时,将新线程变成激活状态。
说人话就是,重新打开与service线程相关标签页就可以
所以我们在有新版本service worker线程更新的时候,可以通知用户刷新界面,以获取最新内容
你可能会感到好奇,为啥要这么设计呢,直接自动地替换旧线程不好吗,非要搞这么麻烦。
其实这和上面谈到的考量一致,浏览器要不遗余力地保证数据和代码的一致性。
- 假设有个A页面,与之关联的是A版本的service worker线程。
- 现在访问与A页面相同URL,重新打开一个页面B,与B页面关联的是新版本的service worker线程B。
- B版本的线程较A版本的不同,是对某个资源的缓存策略由只从缓存中获取,改成优先从网络中获取。
- 这个时候,就极有可能会出现A、B两个页面内容出现不一致的情况。
而这正是我们要极力避免的情况。
激活中(activating)状态
这个状态表示service worker线程即将变成可以控制页面的线程了。
刚进入这个阶段,就会触发service worker线程全局上下文的activate事件。
这个状态有点特殊,不能发送请求。我也不知道为啥,书里面就这么写的
这个状态可以用来做什么呢?可以清楚前一个版本的缓存
这很重要
let cacheVersion = 2;
const cacheName = 'cache_v' + cacheVersion;
function clearCache(){
return self.caches.keys().then((keys)=>{
keys.map(key=>{
if(key !== cacheName){
self.caches.delete(key);
}
})
})
}
self.addEventListener('activate', ()=>{
event.waitUnitl(
Promise.all(
[clearCache(), self.clients.claim()]
)
)
})
上面的代码写是在activate事件中清除老的缓存。
- 有新版的线程进入激活状态后,就会去删除旧版本的缓存。我们将缓存的版本用变量名存起来,如果想要更新缓存的版本,修改
cacheVersion
的值就好了。 - 这一步是必须的。设想一下前后端有一个接口更新了参数,并且返回的数据结构也变了。这时如果还使用缓存里面的响应数据,页面就会报错。
event.waitUntil()
的作用和上面的相同,让线程的状态停留在activating,直到所有的旧缓存都被清除。self.clients.claim()
的作用是让service worker线程直接控制客户端
我试了下,加不加它都一样,不知道是不是我没用对
已激活(activated)状态
这个状态表示service worker线程正在控制一个或多个客户端,并且可以监听页面的fetch请求。
刚进入这个阶段就会触发serviceWorkerContainer对象的oncontrollerchange事件。
如果页面先前没有任何service worker线程注册的话,也可以使用serviceWorkerContainer对象的ready来监测线程是否刚进入这个状态
navigator.serviceWorker.ready.then(()=>{
console.log('there is a service worker thread');
})
ready指向一个promise,其会在页面拥有已激活状态的线程立马解决
已失效(redundant)状态
当service worker线程终止时,就会进入该阶段,无论是被self.skipWaiting()
终止,还是没有了控制的客户端
刚进入该状态的时机,可以通过ServiceWorker的onstatechange事件监测。
总结
这篇文章讲了service worker线程的6个状态,分别从含义、作用、事件、用法几个角度进行阐述。全篇看下来后,相信你对service worker线程的版本控制有更深刻的理解。这一切都是为了前端代码和资源的一致性。
下篇文章讲caches对象,或者是service worker的最佳实践。
转载自:https://juejin.cn/post/7119659841089437704