ServiceWorker生命周期
ServiceWorker本身是有状态的(installing,installed,activating,activated,redundant),这些状态构成了ServiceWorker生命周期:
生命周期可细分为两个过程:
- 安装过程(installing,installed)
- 激活过程(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')
})
- 可通过
registration.installing
属性访问处于install
状态的ServiceWorker; - 在
ServiceWorkerGlobalScope
中可以绑定多个install
事件回调函数,多个事件回调函数的参数是同一个InstallEvent对象; - 如果install事件回调函数抛异常,则视为安装失败,ServiceWorker会进入
redundant
状态; - 如果回调函数里调用
installEvent.waitUntil(promise1)
方法,则promise1被resolved时ServiceWorker对象才被installed,如果promise1被rejected则SW安装失败(redundant); - 同一个事件回调函数或者多个事件回调函数可以多次调用
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)
}))
})
观察控制台输出
1.2 waitUntil方法
InstallEvent
对象从父对象ExtendableEvent
继承了waitUntil方法。该方法延长了事件对象的生存期。当传给waitUntil
方法的Promise对象转成终态(resolved/rejected)才认为事件对象被处理完了。在事件对象被处理完之前ServiceWorker不会进入下一个状态(installed或者redundant)。
2. installed/waiting
ServiceWorker成功安装后便进入installed
状态。至此Service完成了安装过程
,等待进入激活过程
。
- 可通过
registration.waiting
属性访问处于installed
状态的ServiceWorker(见上例); - 如果页面没有激活的ServiceWorker管理,则ServiceWorker进入
activating
状态。
3. activating
ServiceWoker安装成功后进入activating
状态。
3.1 ActivateEvent对象
- 处于
activating
时会触发ServiceWorkerGlobalScope上下文的activate
事件。 - 可以绑定多个事件回调函数,多个事件回调ActivateEvent对象是同一个对象;
- 如果回调函数里调用
activateEvent.waitUntil(promise1)
方法,则表示promise1被resolved/rejected时SW对象才进入下一个状态activated
; -
同一个事件回调函数或者多个事件回调函数可以多次调用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) })) })
控制台输出:
-
跟
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) })) })
控制台输出:
也就是说激活过程中的任何错误不影响ServiceWoker被激活(脑洞下:新SW激活过程中说明页面已经没有被其他SW控制了,所以activate事件回调函数的执行失败并不会影响SW被激活。),注意这一点跟其他文档描述的可能不太一样。
4. activated
这时ServiceWorker可以控制页面了,可以监听功能事件了,如fetch,push事件。默认情况下ServiceWorker只能控制在其激活成功后才加载完成的页面。
4.1 fetch事件 & FetchEvent对象
ServiceWorker控制页面后就可以监听fetch事件了: 捕获请求,构建响应。注意:保证一个请求只有一个响应
- 可以绑定多个fetch事件,并且多个回调函数的fetchEvent是同一个对象;
- 如果回调函数内成功调用了
fetchEvent.respondWith
方法,则后面的回调函数不会被执行; - 回调函数里多次调用
fetchEvent.respondWith
会报错的,即一个request只能有一个reponse; - 回调函数里最好是同步的方式调用
fetchEvent.respondWith
,异步调用不会阻止后面的后调函数执行, 很容易会造成多个reponse,导致错误3; - 如果所有的回调函数里都没有调用
fetchEvent.respondWith
方法则会采用浏览器默认的fetch事件回调函数处理方式,即走网络请求; - 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
状态。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。