命令式快取指南

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

部分網站可能需要與服務工作站通訊,但不需要收到結果通知。例如:

  • 網頁會將網址清單傳送給 Service Worker,用於預先快取,這樣當使用者點選連結時,文件或網頁子資源就會已在快取中,讓後續導覽更快速。
  • 這個頁面會要求 Service Worker 擷取並快取一組熱門文章,以便離線使用。

將這類非重要工作委派給服務工作站,可讓主執行緒釋出,以便更妥善地處理更緊急的工作,例如回應使用者互動。

圖表:網頁要求將資源快取至 Service Worker。

在本指南中,我們將探討如何使用標準瀏覽器 API 和 Workbox 程式庫,實作從網頁到服務工作者的單向通訊技巧。我們將這類用途稱為強制快取

實際工作環境案例

1-800-Flowers.com 透過 postMessage() 使用服務工作者實作強制快取 (預先載入),預先載入分類頁面中的熱門商品,加快後續前往產品詳細資料頁面的速度。

1-800 Flowers 的標誌。

它們會使用混合方法決定要預先載入哪些項目:

  • 在頁面載入期間,他們會要求服務器 worker 擷取前 9 項項目的 JSON 資料,並將產生的回應物件加入快取。
  • 至於其他項目,則會監聽 mouseover 事件,這樣當使用者將游標移至項目上方時,就能觸發「需求」的資源擷取作業。

他們使用 Cache API 來儲存 JSON 回應:

1-800 Flowers 的標誌。
從 1-800Flowers.com 的產品資訊頁面預先擷取 JSON 產品資料。

使用者點選項目時,系統可以從快取中擷取與該項目相關聯的 JSON 資料,而不需要連線,因此可加快導覽速度。

使用 Workbox

Workbox 提供簡單的方式,可透過 workbox-window 套件 (一組要在視窗內容中執行的模組),將訊息傳送至服務工作者。這些是服務工作站中執行的其他 Workbox 套件的補充項目。

如要讓頁面與服務工作者通訊,請先取得已註冊服務工作者的 Workbox 物件參照:

const wb = new Workbox('/sw.js');
wb.register();

接著,您可以直接以宣告方式傳送訊息,不必費心取得註冊、檢查啟用狀態,或考慮底層通訊 API:

wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });

服務工作站會實作 message 處理程序,用於監聽這些訊息。您可以選擇傳回回應,但在下列情況下,這並非必要:

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PREFETCH') {
    // do something
  }
});

使用瀏覽器 API

如果 Workbox 程式庫無法滿足您的需求,請參閱以下說明,瞭解如何使用瀏覽器 API 實作視窗與服務 worker 之間的通訊。

postMessage API 可用於建立從網頁傳送至服務工作者的單向通訊機制。

該頁面會在服務工作者介面上呼叫 postMessage()

navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
  payload: 'some data to perform the task',
});

服務工作站會實作 message 處理程序,用於監聽這些訊息。

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === MSG_ID) {
    // do something
  }
});

{type : 'MSG_ID'} 屬性並非絕對必要,但這是讓網頁向 Service Worker 傳送不同類型指令的一種方式 (也就是「預先擷取」與「清除儲存空間」)。服務工作站可以根據這個標記分支至不同的執行路徑。

如果操作成功,使用者就能獲得相關好處,但如果失敗,則不會影響主要使用者流程。舉例來說,當 1-800-Flowers.com 嘗試預快取時,網頁不需要知道服務工作者是否成功。如果是這樣,使用者就能享有更快速的導覽體驗。如果不是,頁面仍需要導向新頁面。只是需要多一點時間。

簡單的預先載入範例

強制快取最常見的應用之一是預先擷取,也就是在使用者前往特定網址之前,先擷取該網址的資源,以便加快導覽速度。

在網站中實作預先載入功能的方法有很多種:

對於較為簡單的預先載入情境,例如預先載入文件或特定資產 (JS、CSS 等),這些技巧是最佳做法。

如果需要額外邏輯 (例如解析預先載入資源 (JSON 檔案或頁面),以便擷取內部網址),建議您將這項工作完全委派給服務工作員。

將這類作業委派給服務工作者有以下優點:

  • 將擷取和擷取後處理 (稍後會介紹) 的繁重工作卸載至次要執行緒。這樣一來,主執行緒就能釋放資源,處理更重要的工作,例如回應使用者互動。
  • 允許多個用戶端 (例如分頁) 重複使用常見功能,甚至同時呼叫服務,而不會封鎖主執行緒。

預先擷取產品詳細資料頁面

首先,請在服務工作者介面上使用 postMessage(),並將網址陣列傳遞至快取:

navigator.serviceWorker.controller.postMessage({
  type: 'PREFETCH',
  payload: {
    urls: [
      'www.exmaple.com/apis/data_1.json',
      'www.exmaple.com/apis/data_2.json',
    ],
  },
});

在服務工作站中,實作 message 處理常式,以攔截及處理任何有效分頁傳送的訊息:

addEventListener('message', (event) => {
  let data = event.data;
  if (data && data.type === 'PREFETCH') {
    let urls = data.payload.urls;
    for (let i in urls) {
      fetchAsync(urls[i]);
    }
  }
});

在前述程式碼中,我們引入了名為 fetchAsync() 的小型輔助函式,用於對網址陣列進行疊代,並為每個網址發出擷取要求:

async function fetchAsync(url) {
  // await response of fetch call
  let prefetched = await fetch(url);
  // (optionally) cache resources in the service worker storage
}

取得回應後,您可以依賴資源的快取標頭。不過,在許多情況下 (例如產品詳細資料頁面),系統不會快取資源 (也就是說,這些資源的 Cache-control 標頭為 no-cache)。在這種情況下,您可以將擷取的資源儲存在服務工作者快取中,藉此覆寫這項行為。這項做法還有另一項好處,就是允許在離線情境中提供檔案。

不只 JSON 資料

從伺服器端點擷取 JSON 資料後,通常會包含其他值得預先載入的網址,例如與這類第一層資料相關聯的圖片或其他端點資料。

假設在本範例中,傳回的 JSON 資料是雜貨購物網站的資訊:

{
  "productName": "banana",
  "productPic": "https://cdn.example.com/product_images/banana.jpeg",
  "unitPrice": "1.99"
 }

修改 fetchAsync() 程式碼,以便逐一檢視產品清單,並為每個產品快取主打圖片:

async function fetchAsync(url, postProcess) {
  // await response of fetch call
  let prefetched = await fetch(url);

  //(optionally) cache resource in the service worker cache

  // carry out the post fetch process if supplied
  if (postProcess) {
    await postProcess(prefetched);
  }
}

async function postProcess(prefetched) {
  let productJson = await prefetched.json();
  if (productJson && productJson.product_pic) {
    fetchAsync(productJson.product_pic);
  }
}

您可以針對 404 等情況,在這個程式碼周圍新增一些例外狀況處理程序。不過,使用 Service Worker 預先載入的優點是,即使失敗,對網頁和主執行緒的影響也不大。您也可以在預先擷取內容的後置處理程序中加入更複雜的邏輯,讓後置處理程序更具彈性,並與處理的資料解耦。創意無極限。

結論

在本文中,我們將介紹網頁與服務工作單元之間單向通訊的常見用途:強制快取。我們討論的範例只是展示使用這個模式的一種方式,同樣做法也適用於其他用途,例如根據需求快取熱門文章,以便離線使用、加入書籤等。

如要進一步瞭解頁面和服務工作單元通訊的其他模式,請參閱:

  • 廣播更新:從服務工作者呼叫網頁,以便通知重要更新 (例如推出新版的 webapp)。
  • 雙向通訊:將工作委派給服務工作者 (例如大量下載),並持續向頁面提供進度資訊。