前端日拱一卒D3——Service Worker

DerekZ95發表於2018-07-19

前言

餘為前端菜鳥,感姿勢水平匱乏,難觀前端之大局。遂決定循前端知識之脈絡,以興趣為引,輔以幾分堅持,望於己能解惑致知、於同道能助力一二,豈不美哉。

本系列程式碼及文件均在 此處

簡介

service worker是一個由來已久的HTML5 API,旨在建立持久的離線快取。類似於web worker,都是在瀏覽器後臺單獨開的一個執行緒內工作,可以向客戶端推訊息,不可操作dom。

service worker有自己的worker context,可以攔截請求和返回,快取檔案,必須在HTTPS或者本地環境下執行,非同步實現,內部大部分通過Promise實現

依賴Cache API,依賴FTML5 fetch API,依賴Promise實現離線快取功能

生命週期

parsed -> installing -> installed -> activating -> activated -> redundant

引用

註冊

註冊在主執行緒中進行

if (navigator.serviceWorker) {
  // register非同步方法
  navigator.serviceWorker.register('./sw.js', { scope: '/' }).then(() => {
    console.log('sw service worker 註冊成功')
    console.log('parsed ----> installing')
  }).catch((err) => {
    console.log(err)
  })
}
複製程式碼
  • 註冊時的scope可選,預設是sw.js所在目錄,表示該worker可以接收該目錄下的所有fetch事件
  • 每次呼叫egister會判斷worker是否已註冊再進行處理
  • 註冊成功的worker有了自己的worker context,此時表示worker的指令碼被成功解析,轉入installing狀態

安裝

在worker的指令碼檔案中監聽install事件,可以維護初始快取列表

self.addEventListener('install', (event) => {
  // 確保安裝完成前完成下面的操作
  event.waitUntil(
    // 建立一個cache
    caches.open('v1').then((cache) => {
      // cache是快取例項
      // 呼叫該例項的addAll方法提前載入相關檔案進快取
      return cache.addAll([
        '/html/test.js',
        '/html/default.html'
      ])
    })
    console.log('installing ----> installed ----> activating')
  )
  // self.skipWaiting() 直接進入activate
})
複製程式碼

啟用

worker安裝完成後會轉為installed/activating狀態,在滿足以下條件之一時可以轉為activate狀態

  • 沒有active worker在執行或者舊的worker被釋放(頁面關閉)
  • 呼叫self.skipWaiting()跳過waiting,進入activate狀態

activated狀態會觸發activate回撥,在worker監本中監聽activate事件

self.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys().then(function (cacheNames) {
      return Promise.all(
        cacheNames.filter(function (cacheName) {
          return cacheName != 'v1';
        }).map(function (cacheName) {
          // 清除舊的快取
          return caches.delete(cacheName);
        })
      )
    })
    console.log('activating ----> activated')
  )
  // self.clients.claim() 獲取頁面控制權,舊的worker失效
})
複製程式碼

啟用後的控制

啟用後可以控制頁面行為,可以處理功能事件,如fetch, push, message

符合worker的scope內的資源請求都會觸發fetch被控制

self.addEventListener('fetch', function (event) {
  // 利用respondWith劫持響應
  event.respondWith(
    caches.open('v1').then(function (cache) {
      return cache.match(event.request).then(function (response) {
        // match到則返回否則直接請求
        return response || fetch(event.request).then(function (response) {
          // 404拋錯到catch處理
          if (response.status === 404) {
            throw new Error('nothing')
          }
          // 將response作為value存入cache
          // install時可以進行快取
          // 劫持fetch時也可以進行動態資源快取
          cache.put(event.request, response.clone());
          return response;
        }).catch((err) => {
          // 返回cache storage裡存的預設頁面
          return caches.match('/html/default.html');
        });
      });
    })
  );
});
複製程式碼

localStorage是同步的,所以不能用於service worker內的儲存,IndexedDB可以用

廢棄狀態

安裝失敗、啟用失敗、被新的worker取代時轉入廢棄狀態

chrome中檢視

前端日拱一卒D3——Service Worker

更新

當service worker指令碼內容更新時,會安裝新的檔案並觸發install,轉入installed/waiting狀態。此時舊的worker仍處於啟用狀態,在頁面關閉後會被廢棄,此後新開頁面裡新的worker才會生效

前端日拱一卒D3——Service Worker

上圖為更改sw.js後重新整理頁面的結果,關閉頁面重開後1497將是啟用中的worker

強制更新和檢查更新

一般為24小時

自動更新worker

// 跳過等待,直接進入activate
self.addEventListener('install', function (event) {
    event.waitUntil(self.skipWaiting());
});
// actived之前更新客戶端
self.addEventListener('activate', function (event) {
  event.waitUntil(
    Promise.all([
      // 更新客戶端所有的service worker
      self.clients.claim(),
      // 清理舊版本
      caches.keys().then(function (cacheList) {
        return Promise.all(
          cacheList.map(function (cacheName) {
            if (cacheName !== 'v1') {
              return caches.delete(cacheName);
            }
          })
        )
      })
    ])
  )
)}
複製程式碼

手動更新

主執行緒內每次註冊時進行更新

var version = '1.0';
navigator.serviceWorker.register('./sw.js').then(function (reg) {
  if (localStorage.getItem('sw_version') !== version) {
    // reg.update
    reg.update().then(function () {
      localStorage.setItem('sw_version', version)
    });
  }
});
複製程式碼

debug時更新

self.addEventListener('install', function () {
  if (ENV === 'development') {
    // 每次重新整理頁面重新註冊安裝時直接進入activate,確保最新
    self.skipWaiting();
  }
});
複製程式碼

cacheStorage

cacheStorage是在serviceworker規範中定義的介面,我們可以使用全域性的caches訪問cacheStorage

caches常用api有: open, match, delete, has, keys

cache storage

例項

程式碼位於github

steps

  • 本地利用koa-static建一個靜態頁面的server

    const Koa = require('koa')
    const path = require('path')
    const static = require('koa-static')
    const app = new Koa()
    const staticPath = './frontend/basic'
    app.use(static(
        path.join(__dirname, staticPath)
    ))
    複製程式碼
  • 頁面中插入指令碼

    window.addEventListener('load', function () {
        if (navigator.serviceWorker) {
            navigator.serviceWorker.register('./sw.js').then(() => {
                console.log('sw service worker 註冊成功')
            }).catch((err) => {
                console.log(err)
            })
        }
    })
    複製程式碼
  • sw.jsgithub

最終效果

  • offline模式下,可以從cache中讀取檔案快取

    from service worker

  • 不存在的資源路徑返回快取好的default.html

  • 二次訪問快取list內的資源時劫持請求和響應,返回快取內容

    cache storage

雖發表於此,卻畢竟為一人之言,又是每日學有所得之筆記,內容未必詳實,看官老爺們還望海涵。

相關文章