JS-service worker生命周期

887 阅读7分钟

前言

在上篇文章(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规范里提到了这个状态,而是这个状态有几件很重要的事情,需要被完成。

初始化任务

  1. 确保服务脚本来自相同的源。这个是处于安全的考量,避免加载不安全的脚本

什么是相同的源?脚本的URL,协议相同、路径相同、端口号相同

  1. 确保在安全的上下文中注册service worker线程。这个应该是需要确保通信协议是https,或者IP地址为localhost、127.0.0.1的http协议
  2. 确保service脚本可以被正确解析,不会出现语法错误
  3. 捕获服务脚本的快照,当下一次执行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事件的回调函数中缓存所需要的静态资源。

  1. 有个需要缓存资源的URL列表,把需要缓存的资源的URL填入进去就可以了
  2. event.waitUntil(),接收一个待解决的promise,该方法会等到传入的promise状态解决,才执行完成。作用就是将service worker线程的状态延迟到promise解决,才进入下一阶段。也就是等到所有资源缓存完成才进入下一阶段
  3. 其中也调用了self.skipWaiting(),它的作用是使新版本的线程跳过等待状态,直接替换旧版本的线程
  4. 如果有任何资源缓存失败,都会让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事件中清除老的缓存。

  1. 有新版的线程进入激活状态后,就会去删除旧版本的缓存。我们将缓存的版本用变量名存起来,如果想要更新缓存的版本,修改cacheVersion的值就好了。
  2. 这一步是必须的。设想一下前后端有一个接口更新了参数,并且返回的数据结构也变了。这时如果还使用缓存里面的响应数据,页面就会报错。
  3. event.waitUntil()的作用和上面的相同,让线程的状态停留在activating,直到所有的旧缓存都被清除。
  4. 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的最佳实践。