服務工作處理程序的思維

在考慮服務工作人員時應如何思考。

服務工作人員功能強大,也絕對值得學習。可讓您為使用者提供全新程度的體驗。你的網站可以立即載入。並可在離線狀態下運作。它可以安裝做為平台專用的應用程式,並且具有精美的質感,同時還能觸及並自由運用網路。

但是,服務工作處理程序與多數網路開發者不同。他們會有陡峭的學習曲線,還需要注意一些麻煩。

Google Developers 和最近一起合作進行「Service Workies」這項免費遊戲專案。在建構機構並與服務工作者的複雜內部和外部工作時,我遇到了幾個小問題。讓我最開心的一件事想出了少量描述的隱喻。在這篇文章中,我們會探討這些心理模型,並包容我們的大腦具備各種特徵,是服務工作人員陷入難關的困境。

相同,但不同

為服務工作人員編寫程式碼時,許多事情對您來說會感到熟悉。您可以體驗自己喜愛的新 JavaScript 語言功能。監聽生命週期事件,和使用 UI 事件一樣。您可以依據熟悉的承諾來管理控制流程。

但其他 Service Worker 的行為會導致你感到困惑。尤其是在您重新整理頁面後,系統並未套用程式碼變更時更是如此。

新圖層

一般而言,建置網站時只需要考量用戶端和伺服器這兩層。Service Worker 是位於中間的新圖層。

Service Worker 可做為用戶端和伺服器之間的中間層

服務工作人員可以視為一種瀏覽器擴充功能,讓網站可以在使用者的瀏覽器中安裝。安裝完成後,服務工作處理程序會使用強大的中間層來「擴充」extends您網站的瀏覽器。這個 Service Worker 層可攔截和處理網站發出的所有要求。

Service Worker 層的生命週期與瀏覽器分頁無關。簡單重新整理頁面不足以更新 Service Worker,就像不要重新整理頁面來更新部署在伺服器上的程式碼。每個圖層的更新規則都不盡相同。

Service Workies 遊戲中,我們會介紹服務工作工作站生命週期的許多細節,並提供大量的使用練習。

功能強大,但有限制

在網站上聘請服務人員,能為您帶來非凡的優勢。網站可以:

  • 就算使用者離線,也能穩定運作
  • 透過快取大幅改善效能
  • 使用推播通知
  • 皆以 PWA 形式安裝

儘管服務工作人員可以做到多少,但卻受限於設計。但無法同步或位於與網站相同的執行緒中。也就是說,您將無法存取:

  • localStorage
  • DOM
  • 窗戶

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

長壽,但短壽

即使使用者離開您的網站或關閉分頁,,仍會啟用的服務工作人員繼續運作。瀏覽器會一直留在這個服務工作處理程序,讓使用者在下次回到您的網站時能找到所需內容。在送出第一個要求之前,服務工作人員有機會攔截要求並且取得網頁控制權。這就是網站可以離線運作的原因,服務工作人員即使沒有網際網路連線,服務工作人員也可以提供網頁的快取版本。

Service Workies 中,我們會使用 Kolohe (友善的服務工作人員) 攔截及處理要求,以視覺化的方式呈現這個概念。

已停止

儘管服務工作人員似乎是非凡,但幾乎隨時都有停止服務工作人員。瀏覽器不想浪費資源在服務工作處理程序中正在執行的作業。停止工作與終止不同,服務工作處理程序會保持安裝及啟用狀態。它只是睡著了。下次需要使用時 (例如處理要求),瀏覽器就會將其喚醒。

waitUntil

由於服務工作人員總是可能進入休眠狀態,因此在發生重要活動時,您的服務工作人員需要讓瀏覽器知道何時發生重要事情,但感覺不太像是小睡。這時 event.waitUntil() 就能派上用場。這個方法會延長其所用的生命週期,除了能保持停止狀態,也不會進入生命週期的下一個階段,直到我們準備就緒為止。讓我們有時間設定快取、從網路擷取資源等。

這個範例會通知瀏覽器要等到 assets 快取建立完成,並填入 sWord 的圖片後,服務工作處理程序才會完成安裝:

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

注意全球各國

當系統啟動/停止作業時,系統會重設 Service Worker 的全域範圍。因此,請小心不要在 Service Worker 中使用任何全域狀態,否則下次醒來時就會感到難過,而且狀態與預期的不同。

請考慮使用全域狀態的示例:

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

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

這個服務工作處理程序會記錄一個號碼,比如 0.13981866382421893hasHandledARequest 變數也會變更為 true。現在服務工作處理程序會閒置一小段時間,因此瀏覽器會將其停止。下次有要求時,服務工作處理程序會再次用到,因此瀏覽器會將其喚醒。系統也會再次評估指令碼的指令碼。現在 hasHandledARequest 已重設為 falsefavoriteNumber 則完全不同:0.5907281835659033

您無法依賴 Service Worker 中的儲存狀態。此外,建立訊息管道這類項目會造成錯誤:您每次服務工作處理程序停止/啟動時,您都會取得全新的例項。

我們在服務工作團隊第 3 章中,看到已停止的 Service Worker 會在等待喚醒時失去所有顏色。

停止的 Service Worker 示意圖

集中管理

您一次只能由一個 Service Worker 控制網頁。但可以一次「安裝」兩個 Service Worker。變更服務工作人員程式碼並重新整理頁面時,實際上並未編輯服務工作處理程序。Service Worker 無法變更。您決定改為建立新的清單,這個新的 Service Worker (我們稱之為 SW2) 會安裝,但目前還無法啟用。目前的 Service Worker (SW1) 必須等待終止 (使用者離開你的網站時)。

接收其他 Service Worker 的快取

安裝時,SW2 可以開始進行設定,通常是建立和填入快取。但是請注意,這個新的服務工作處理程序可以存取目前 Service Worker 能存取的所有項目。如果您不小心混亂,新進服務員工可能只會對目前的服務工作人員造成困擾。以下是一些可能造成問題的例子:

  • SW2 可以刪除 SW1 正在使用的快取。
  • SW2 可以編輯 SW1 使用的快取內容,導致 SW1 傳回網頁非預期的素材資源。

略過等待中

Service Worker 也可使用具風險的 skipWaiting() 方法,在安裝完成後立即控制網頁。除非您刻意嘗試替換發生錯誤的 Service Worker,否則這通常不是壞事。新 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 狀態時,您就知道服務已經接管,而先前的 Service Worker 具有備援能力。此時,請務必清除舊版 Service Worker 後再進行清理。如此一來,不僅會遵守使用者的快取儲存空間限制,還能避免意外發生錯誤。

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 思維

考量到服務工作人員的心態有助於您滿載而歸。瞭解最佳做法後,您就能為使用者打造非凡的體驗。

如果想在玩遊戲的過程中瞭解上述所有好處,那就太棒了!開始玩服務工作組,瞭解服務工作人員如何過勞,培育離線野獸。