服務工作處理程序的思維

如何思考服務工作者。

Service Worker 的力量強大,絕對值得學習。讓您為使用者提供全新等級的體驗。網站可以立即載入。可離線運作。這類應用程式可做為特定平台應用程式安裝,並提供精緻的使用體驗,同時享有網頁的觸及範圍和自由度。

但服務工作站與大多數網頁開發人員所熟悉的任何東西都不同。不僅容易學習,而且還多了幾隻苦事要小心。

Google Developers 與我最近合作推出 Service Workies 專案,這是讓服務人員瞭解服務工作的免費遊戲。在建構服務 worker 的複雜內部和外部作業時,我遇到了一些問題。對我來說,最有幫助的是提出一些具象比喻。在本篇文章中,我們將探討這些心智模型,並深入瞭解服務端代理程式既複雜又強大的矛盾特徵。

相同,但不同

編寫服務工作程式碼時,您會發現許多內容都很熟悉。您可以使用自己最喜歡的全新 JavaScript 語言功能。監聽生命週期事件的方式與 UI 事件相同。您可以使用承諾管理控制流程,就像以往一樣。

但其他服務工作程式行為會讓您一頭霧水。尤其是在重新整理頁面後,程式碼變更未套用時。

新圖層

通常在建構網站時,您只需要考慮兩個層面:用戶端和伺服器。服務工作者是位於中間的全新層級。

服務工作者是用戶端與伺服器之間的中介層

服務工作處理程序就像是瀏覽器擴充功能,一種可以在使用者的瀏覽器中安裝。安裝完成後,Service Worker 會使用強大的中層「擴充」網站的瀏覽器。這個服務工作架構層可攔截並處理網站發出的所有要求。

服務工作者層有自己的生命週期,不受瀏覽器分頁影響。只重新整理網頁並不足以更新 Service Worker,只是您並不希望重新整理頁面來更新部署在伺服器上的程式碼。每個圖層都有各自的更新規則。

Service Workies 遊戲中,我們涵蓋了 Service Worker 生命週期的許多詳細資料,我們會針對這個生命週期提供一些實務做法。

功能強大,但功能有限

在網站上使用 Service Worker 可帶來許多好處。你的網站可以:

  • 即使使用者處於離線狀態,也能正常運作
  • 透過快取大幅提升效能
  • 請使用推播通知
  • PWA 形式安裝

雖然服務工作程式功能多元,但設計上仍有限制。他們無法執行任何同步作業,也無法在與您的網站相同的執行緒中執行作業。這表示無法存取以下項目:

  • localStorage
  • DOM
  • 窗戶

好消息是,您的網頁可透過多種方式與服務工作人員溝通,包括直接 postMessage、一對一訊息管道,以及一對多廣播頻道

長壽,但壽命短

即使使用者離開網站或關閉分頁,仍會保留服務工作程式。瀏覽器會保留這個 Service Worker,以便在使用者下次造訪網站時使用。在首次要求發出之前,服務工作者有機會攔截該要求並控制網頁。這就是網站可以離線運作的原因,就算使用者沒有連上網際網路,服務工作人員也可以提供網頁本身的快取版本。

服務工作程式中,我們透過 Kolohe (友善的服務工作程式) 攔截及處理要求,以圖像化方式呈現這個概念。

已停止

儘管服務工作處理程序看似不重要,但他們幾乎隨時都可能停止。瀏覽器不想在目前沒有任何動作的 Service Worker 上浪費資源。停止與終止不同,服務工作者仍會安裝並啟用。只是讓裝置進入休眠狀態。下次需要時 (例如處理要求),瀏覽器就會喚醒該服務。

waitUntil

由於服務工作者隨時都有可能處於休眠狀態,因此需要透過某種方式讓瀏覽器知道何時正在執行重要作業,而不需要休眠。這時 event.waitUntil() 就能派上用場。這個方法會延長生命週期,讓生命週期不會在未準備就緒前停止或進入下個階段。這樣一來,我們就能有時間設定快取、從網路擷取資源等。

這個範例會告訴瀏覽器,在建立 assets 快取並填入劍的圖片之前,服務工作站的安裝作業不會完成:

self.addEventListener("install", event => {
  event.waitUntil(
    caches.open("assets").then(cache => {
      return cache.addAll(["/weapons/sword/blade.png"]);
    })
  );
});

留意全球狀態

這個開始/停止作業發生時,系統會重設 Service Worker 的全域範圍。因此,請務必避免在服務工作者中使用任何全域狀態,否則下次服務 worker 喚醒時,如果狀態與預期不同,您將會感到失望。

請參考使用全域狀態的範例:

const favoriteNumber = Math.random();
let hasHandledARequest = false;

self.addEventListener("fetch", event => {
  console.log(favoriteNumber);
  console.log(hasHandledARequest);
  hasHandledARequest = true;
});

這個 Service Worker 會在每次要求時記錄一個數字—假設 0.13981866382421893hasHandledARequest 變數也會變更為 true。服務工作站現在處於閒置狀態,因此瀏覽器會停止它。下次收到要求時,服務工作處理程序必須再次,瀏覽器才會喚醒工作。系統會重新評估指令碼指令碼。hasHandledARequest 會重設為 false,而 favoriteNumber 則會變成完全不同的 0.5907281835659033

您無法依賴服務工作站中的儲存狀態。此外,建立訊息管道等項目的例項可能會導致錯誤:每次服務工作程式停止/啟動時,您都會取得全新的例項。

第 3 章「Service Worker」中,我們將停止的 Service Worker 視為在等待喚醒時失去所有顏色。

已停止的服務工作者視覺化圖表

相輔相成,

您的網頁一次只能由一個 Service Worker 控管。但可以同時安裝兩個服務工作者。當您變更服務工作者程式碼並重新整理網頁時,您實際上並未編輯服務工作者。服務工作者無法變更。而是建立全新的帳戶。這個新的服務工作者 (我們稱之為 SW2) 會安裝,但不會啟用。必須「等待」目前的 Service Worker (SW1) 終止 (使用者離開您的網站時)。

干擾其他服務工作程的快取

安裝期間,SW2 可以完成設定,通常是建立並填入快取。不過請注意,這個新的服務工作程式可存取目前服務工作程式可存取的所有內容。如果你不小心,新的等待中 Service Worker 可能會對目前的 Service Worker 造成嚴重影響。以下列舉幾個可能會導致你遇到問題的情況:

  • SW2 可能會刪除 SW1 正在使用的快取。
  • SW2 可以編輯 SW1 使用的快取內容,導致 SW1 回應該網頁預期之外的資產。

略過等待等待

Service Worker 也可以使用風險較高的 skipWaiting() 方法,在安裝完成後立即控管網頁。除非您有意嘗試更換故障的 Service Worker,否則這通常不是問題。新的服務工作者可能會使用目前網頁未預期的更新資源,導致錯誤和錯誤。

清理起步

要避免服務工作處理程序彼此同步,方法就是確保他們使用不同的快取。最簡單的方法就是為所使用的快取名稱建立版本。

const version = 1;
const assetCacheName = `assets-${version}`;

self.addEventListener("install", event => {
  caches.open(assetCacheName).then(cache => {
    // confidently do stuff with your very own cache
  });
});

部署新的 Service Worker 時,您將串場 version,使其使用與上一個 Service Worker 之間完全獨立的快取,執行需要的工作。

快取的視覺化呈現

結束清理

服務工作者達到 activated 狀態後,您就知道它已接管,而先前的服務工作者已不必要。此時,請務必清理舊服務工作程式。這不僅能尊重使用者的快取儲存空間限制,還能避免不經意產生的錯誤。

caches.match() 方法是常用的捷徑,可從任何快取中擷取相符的項目。但會依建立順序逐一檢查快取。假設您在兩個不同的快取 (assets-1assets-2) 中,有兩個版本的指令碼檔案 app.js。您的網頁預期會使用儲存在 assets-2 中的新版指令碼。不過,如果你沒有刪除舊的快取,caches.match('app.js') 就會從 assets-1 傳回舊快取,這很可能導致你的網站損毀。

前一個 Service Worker 後所需的清除工作,就是刪除新 Service Worker 不需要的任何快取:

const version = 2;
const assetCacheName = `assets-${version}`;

self.addEventListener("activate", event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== assetCacheName){
            return caches.delete(cacheName);
          }
        });
      );
    });
  );
});

雖然要避免 Service Worker 彼此衝突需要花費一些心力和紀律,但這一切都是值得的。

Service Worker 思維

在思考服務工作站時,請先調整正確的思維,這樣才能充滿信心地建構服務工作站。一旦掌握這些概念,您就能為使用者打造絕佳的體驗。

如果您想透過玩遊戲來瞭解這一切,那麼您就來對地方了!請前往玩Service Workies,瞭解如何使用服務工作架構來消滅離線怪獸。