1

ServiceWorker生命周期

ServiceWorker本身是有状态的(installing,installed,activating,activated,redundant),这些状态构成了ServiceWorker生命周期:
clipboard.png

生命周期可细分为两个过程:

  1. 安装过程(installing,installed)
  2. 激活过程(activating, activated)

1. installing

当使用方法navigator.serviceWorker.register注册一个新的ServiceWorker时,浏览器会下载JS脚本,解析脚本。这时ServiceWorker处于installing状态。如果安装成功,则进入installed状态,否则会进入redundant状态。

1.1 InstallEvent对象

处于installing时会触发ServiceWorkerGlobalScope上下文的install事件。
index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Service Life Cycle</title>
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
</head>
<body>
<script>
// Check that service workers are registered
if ('serviceWorker' in navigator) {
  // Use the window load event to keep the page load performant
  window.addEventListener('load', () => {
        navigator.serviceWorker.register('sw-lifecyle.js?v=' + 1).then((registration) => {
            var sw = null, state;
            if(registration.installing) {
                sw = registration.installing;
                state = 'installing';
            } else if(registration.waiting) {
                sw = registration.waiting;
                state = 'installed'
            } else if(registration.active) {
                sw = registration.active;
                state = 'activated'
            }
            state && console.log(`sw state is ${state}`);
            if(sw) {
                sw.onstatechange = function() {
                    console.log(`sw state is ${sw.state}`);
                }    
            }
        });
  });
}
</script>
</body>
</html>

sw.js 文件内容:

self.addEventListener('install', function(event) {
    console.log('install callback')
})
  1. 可通过registration.installing属性访问处于install状态的ServiceWorker;
  2. ServiceWorkerGlobalScope中可以绑定多个install事件回调函数,多个事件回调函数的参数是同一个InstallEvent对象;
  3. 如果install事件回调函数抛异常,则视为安装失败,ServiceWorker会进入redundant状态;
  4. 如果回调函数里调用installEvent.waitUntil(promise1)方法,则promise1被resolved时ServiceWorker对象才被installed,如果promise1被rejected则SW安装失败(redundant);
  5. 同一个事件回调函数或者多个事件回调函数可以多次调用installEvent.waitUntil(promise)方法,则表达所有的promise都被resolved时SW对象才被installed,只要存在promise被rejected则SW安装失败(redundant)。

修改sw.js内容,重新注册sw:

self.addEventListener('install', function(event) {
    event._from = 'callback1'
    console.log('install callback 1: ' + (Date.now()/1000))
    var i = 0;
    while(i < 1000000) {
        ++i;
    }
    // 多次调用
    event.waitUntil(new Promise(function(resolve) {
        setTimeout(() => {
            console.log('resolve 2s')
            resolve()
        }, 2000)
    }))
    event.waitUntil(new Promise(function(resolve, reject) {
        setTimeout(() => {
            console.log('resolve 3s')
            resolve() // 
        }, 3000)
    }))
})

// 多次绑定Install事件
self.addEventListener('install', function(event) {
    event._from = 'callback2'
    console.log('install callback 2: ' + (Date.now()/1000))
    event.waitUntil(new Promise(function(resolve) {
        setTimeout(() => {
            console.log('resolve 5s')
            resolve()
        }, 5000)
    }))
})

观察控制台输出

clipboard.png

1.2 waitUntil方法

InstallEvent对象从父对象ExtendableEvent继承了waitUntil方法。该方法延长了事件对象的生存期。当传给waitUntil方法的Promise对象转成终态(resolved/rejected)才认为事件对象被处理完了。在事件对象被处理完之前ServiceWorker不会进入下一个状态(installed或者redundant)。

2. installed/waiting

ServiceWorker成功安装后便进入installed状态。至此Service完成了安装过程,等待进入激活过程

  1. 可通过registration.waiting属性访问处于installed状态的ServiceWorker(见上例);
  2. 如果页面没有激活的ServiceWorker管理,则ServiceWorker进入activating状态。

3. activating

ServiceWoker安装成功后进入activating状态。

3.1 ActivateEvent对象

  1. 处于activating时会触发ServiceWorkerGlobalScope上下文的activate事件。
  2. 可以绑定多个事件回调函数,多个事件回调ActivateEvent对象是同一个对象;
  3. 如果回调函数里调用activateEvent.waitUntil(promise1)方法,则表示promise1被resolved/rejected时SW对象才进入下一个状态activated
  4. 同一个事件回调函数或者多个事件回调函数可以多次调用activateEvent.waitUntil(promise)方法,则表达所有的promise都被resolved/rejected时SW对象才进入下一个状态activated
    修改sw.js文件:

    self.addEventListener('activate', function(event) {
        event._from = 'callback1'
        console.log('activate callback 1: ' + (Date.now()/1000))
        var i = 0;
        while(i < 1000000) {
            ++i;
        }
        event.waitUntil(new Promise(function(resolve, reject) {
            setTimeout(() => {
                console.log('resolve 2s')
                resolve()
            }, 2000)
        }))
        event.waitUntil(new Promise(function(resolve, reject) {
            setTimeout(() => {
                console.log('resolve 4s')
                resolve()
            }, 4000)
        }))
    })
    
    self.addEventListener('activate', (event) => {
        event.waitUntil(new Promise((resolve, reject) =>{
            setTimeout(() => {
                resolve('resolve activate')
            }, 5000)
        }))
    })

    控制台输出:
    clipboard.png

  5. install事件回调函数不一样的是在activate事件回调函数里抛异常,或者传给activateEvent.waitUntil(promise1)方法的promise被reject都不会影响ServiceWorker进入activated状态。
    再次修改sw.js:

    self.addEventListener('activate', function(event) {
        event._from = 'callback1'
        console.log('activate callback 1: ' + (Date.now()/1000))
        var i = 0;
        while(i < 1000000) {
            ++i;
        }
        event.waitUntil(new Promise(function(resolve, reject) {
            setTimeout(() => {
                console.log('resolve 2s')
                resolve()
            }, 2000)
        }))
        event.waitUntil(new Promise(function(resolve, reject) {
            setTimeout(() => {
                console.log('resolve 4s')
                resolve()
            }, 4000)
        }))
    })
    
    self.addEventListener('activate', (event) => {
        throw 'error'
        event.waitUntil(new Promise((resolve, reject) =>{
            setTimeout(() => {
                resolve('reject activate')
            }, 1)
        }))
    })
    
    self.addEventListener('activate', (event) => {
        event.waitUntil(new Promise((resolve, reject) =>{
            setTimeout(() => {
                reject('reject activate')
            }, 5000)
        }))
    })

    控制台输出:
    clipboard.png

也就是说激活过程中的任何错误不影响ServiceWoker被激活(脑洞下:新SW激活过程中说明页面已经没有被其他SW控制了,所以activate事件回调函数的执行失败并不会影响SW被激活。),注意这一点跟其他文档描述的可能不太一样。

4. activated

这时ServiceWorker可以控制页面了,可以监听功能事件了,如fetch,push事件。默认情况下ServiceWorker只能控制在其激活成功后才加载完成的页面。

4.1 fetch事件 & FetchEvent对象

ServiceWorker控制页面后就可以监听fetch事件了: 捕获请求,构建响应。注意:保证一个请求只有一个响应

  1. 可以绑定多个fetch事件,并且多个回调函数的fetchEvent是同一个对象;
  2. 如果回调函数内成功调用了fetchEvent.respondWith方法,则后面的回调函数不会被执行;
  3. 回调函数里多次调用fetchEvent.respondWith会报错的,即一个request只能有一个reponse
  4. 回调函数里最好是同步的方式调用fetchEvent.respondWith,异步调用不会阻止后面的后调函数执行, 很容易会造成多个reponse,导致错误3;
  5. 如果所有的回调函数里都没有调用fetchEvent.respondWith方法则会采用浏览器默认的fetch事件回调函数处理方式,即走网络请求;
  6. fetch回调函数还可以调用waitUntil方法,来延长FetchEvent事件对象的生命,如果有FetchEent对象还未处理完浏览器是不会自动关闭SW的。

5. redundant

redundant状态是ServiceWorker的终态。 关于serviceWorker如何变成redundant状态在Lavas Service Worker 生命周期the-service-worker-lifecycle参考中列举了3种,但是测试发现激活失败并不会导致哎,见上例。在这本书里Building Progressive Web Apps by Tal Ater的观点貌似论证了我们代码。这里再汇总下进入redundant的case:

  • register失败,如多次调用register,后调用注册的SW会变成redundant
  • 安装失败
  • 被新版本的ServiceWorker替换

再强调下激活过程不会导致ServiceWorker变成redundant状态。

参考

  1. Google 服务工作线程生命周期
  2. Chapter 4. Service Worker Lifecycle and Cache Management
  3. The Service Worker Lifecycle
  4. Lavas Service Worker 生命周期

普拉斯强
2.7k 声望53 粉丝

Coder