Service Worker

Service Worker 讓網頁能擁有與 App 一樣的離線和訊息推播功能。關於離線功能,試想,在使用 App 收信、寫信、刪除信件等動作,都需要將結果丟回伺服器儲存,但在某些環境下並無法一直使用網路連線,因此必須使用一種機制,讓我們仍能順暢的使用這些功能,待網路正常連線,再將剛才所執行的一切動作反應回伺服器。關於訊息推播功能,請參考這裡

Service Worker 的生命週期

Service Worker 的生命週期有以下幾個步驟

Service Worker 的生命週期

圖片來源:Browser push notifications using JavaScript

下文會先說明瀏覽器支援度檢查、Service Worker 的註冊(Register)與下載(Download),然後再接上 Service Worker 的生命週期中的安裝(Install)、啟動(Active)、閒置(Idle)、發生錯誤(Error)、存取(Fetch)、收發訊息(Message)和結束(Terminated)。

瀏覽器支援度檢查

目前支援 Service Worker 基本功能的主流瀏覽器有 Chrome(40+)、Firefox(44+)和 Opera(24+)。若想知道其他功能的支援度,可查看這裡

if ('serviceWorker' in navigator) {
  // 瀏覽器支援 Service Worker,可使用!
}

註冊(Register)與下載(Download)

Service Worker 在註冊完成後,瀏覽器才會開始執行背景安裝。而不管註冊成功或失敗,可使用 Callback 做後續處理。

if ('serviceWorker' in navigator) {
  navigator.serviceWorker
    .register('/serviceWorker.js') // 註冊 Service Worker
    .then(function(reg) {
      console.log('Registration succeeded. Scope is ' + reg.scope); // 註冊成功
    })
    .catch(function(error) {
      console.log('Registration failed with ' + error); // 註冊失敗
    });
}

備註

安裝(Install)

當 Service Worker 註冊成功後,瀏覽器會開始安裝,即觸發 install 事件。install 事件要做的事情是啟動瀏覽器的離線快取能力,說白話就是將cache.addAll()內設定的檔案儲存起來,讓 App 能離線執行這些功能。event.waitUntil()確保 Service Worker 在安裝完畢後才去快取這些檔案。

this.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('v1').then(function(cache) {
      return cache.addAll(['/javascripts/service_workers_test/hello.js']);
    }),
  );
});

啟動(Active)

安裝成功後即啟動。注意,當有多個 Service Worker 時,新安裝的 Service Worker 並不會馬上啟動,必須關閉舊有的所有 Service Worker 後,新的才能啟動。為了節省 Service Worker 的空間,在這裡也可刪除前一個快取的資料。

this.addEventListener('activate', function(event) {
  console.log('activated!');
});

存取(Fetch)

當有任何在 Service Worker 控制範圍底下的 HTTP 請求發出時,都會觸發 fetch 事件,在本例的範圍是http://localhost:3000/。fetch event 會劫持(hijack)這個 HTTP 請求,讓我們能利用respondWith()處理後續回應。caches.match(event.request)檢查發出的 HTTP Request 在快取中是否有相符的項目,若有符合則使用 Service Worker 回應,若無符合則發出真正的 HTTP Request 取得回應,並加入快取以供之後使用。因此,在這裡讓我們能設定客製化的回應。

概念如下圖。

Custom responses to requests

圖片來源:Custom responses to requests

例如,存取 hello.js 這個檔案(舉例來說,直接在網址列打上http://localhost:3000/javascripts/service_workers_test/hello.js),就會得到「Hello from your friendly neighbourhood service worker!」這個回應。

this.addEventListener('fetch', function(event) {
  // 目前存取的資訊
  console.log('Handling fetch event for', event.request.url);

  // 劫持 HTTP Request
  event.respondWith(
    // 檢查快取中是否有可用的資源
    caches.match(event.request).then(function(response) {
      if (response) {
        // 使用 Service Worker 回應
        return new Response('<p>Hello from your friendly neighbourhood service worker!</p>', {
          headers: { 'Content-Type': 'text/html' },
        });
      } else {
        console.log('No response found in cache. About to fetch from network...');
      }

      // Service Worker 沒有設定相對應的回應,發出 HTTP Request
      return fetch(event.request)
        .then(function(response) {
          console.log('Response from network is:', response);
          // 加入快取供之後使用
          return caches.open('v1').then(function(cache) {
            cache.put(event.request, response.clone());
            return response;
          });
        })
        .catch(function(error) {
          // 錯誤處理
          console.error('Fetching failed:', error);
          throw error;
        });
    }),
  );
});

收發訊息(Message)

收發訊息。

this.addEventListener('message', function(e) {
  // e.source is a client object
  e.source.postMessage('Hello! Your message was: ' + e.data);
});

function sendMessage(message) {
  console.log('Send message...');
  return new Promise(function(resolve, reject) {
    var messageChannel = new MessageChannel();
    messageChannel.port1.onmessage = function(event) {
      if (event.data.error) {
        reject(event.data.error);
      } else {
        resolve(event.data);
      }
    };
    navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
  });
}

sendMessage('Hello World!');

Service Worker: Message

Demo

Service Worker Demo

加入快取供之後。

Service Worker Demo

完整程式碼

開發工具

Chrome Server Worker Internals

chrome://serviceworker-internals/

開發工具 - Chrome Server Worker Internals

Chrome DevTools

Chrome 瀏覽器按下 F12 進入開發者模式後,可看到目前 Service Worker 的狀況。

開發工具 - Chrome DevTools

查看各家瀏覽器支援度

the Can I Use Service Workers table

備註

這篇文章還有許多需要補充的地方,會慢慢補起來。

參考資料


Service Worker PWA Worker javascript Chrome DevTools