ServiceWorker系列——ServiceWorker生命週期

ServiceWorker生命週期

ServiceWorker自己是有狀態的(installing,installed,activating,activated,redundant),這些狀態構成了ServiceWorker生命週期:
clipboard.pngjavascript

生命週期可細分爲兩個過程:html

  1. 安裝過程(installing,installed)
  2. 激活過程(activating, activated)

1. installing

當使用方法navigator.serviceWorker.register註冊一個新的ServiceWorker時,瀏覽器會下載JS腳本,解析腳本。這時ServiceWorker處於installing狀態。若是安裝成功,則進入installed狀態,不然會進入redundant狀態。java

1.1 InstallEvent對象

處於installing時會觸發ServiceWorkerGlobalScope上下文的install事件。
index.html:git

<!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 文件內容:github

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:web

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)
    }))
})

觀察控制檯輸出promise

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 生命週期
相關文章
相關標籤/搜索