命令式快取指南

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

某些網站可能需要連線至 Service Worker,不需要通知結果。例子如下:

  • 網頁會將網址清單傳送至「預先擷取」,如此一來,當使用者點選連結文件或網頁子資源時,快取中已存有這些資料,讓後續瀏覽的速度更快。
  • 該頁面會要求服務工作站擷取並快取一組熱門文章,以利離線使用。

將這些類型的非重要工作委派給服務工作站,有助於釋出主執行緒,能更妥善地處理較緊迫的任務,例如回應使用者互動。

圖表顯示網頁要求資源快取至 Service Worker。

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

正式環境案例

1-800-Flowers.com 透過 postMessage()命令式快取 (預先擷取) 與服務工作站搭配使用,預先擷取類別頁面中的熱門項目,以加速使用者後續瀏覽產品詳細資料頁面。

1-800 花的標誌。

他們會運用混合的方法決定要預先擷取的項目:

  • 網頁載入時間時,他們會要求服務工作站擷取前 9 個項目的 JSON 資料,並將產生的回應物件新增至快取。
  • 其餘項目則會監聽 mouseover 事件,因此當使用者將遊標移至項目上方時,就可以依「隨選」觸發資源的擷取作業。

這些會使用 Cache API 來儲存 JSON 回應:

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

使用者點選某個項目時,系統就可以從快取中擷取與該項目相關聯的 JSON 資料,而無須前往網路,以加快瀏覽速度。

使用 Workbox

Workbox 可讓您透過 workbox-window 套件輕鬆傳送訊息至 Service Worker,這是一組在視窗環境中執行的模組。這些元件可補充在 Service Worker 中執行的其他 Workbox 套件。

如要與 Service Worker 通訊頁面,請先取得已註冊 Service Worker 的 Workbox 物件參照:

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

接著,您可以直接透過宣告傳送訊息,省去取得登錄、檢查啟用或開發基礎通訊 API 的麻煩:

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

Service Worker 會實作 message 處理常式來監聽這些訊息。您也可以選擇傳回回應,不過在這類情況下並非必要:

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

使用瀏覽器 API

如果 Workbox 程式庫不足以滿足您的需求,您可以使用以下方法,使用瀏覽器 API 實作視窗,服務工作站通訊。

postMessage API 可用來建立從網頁到服務工作站的「單向」通訊機制。

該頁面會在 Service Worker 介面中呼叫 postMessage()

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

Service Worker 會實作 message 處理常式來監聽這些訊息。

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

{type : 'MSG_ID'} 屬性並非必要,但可以讓網頁傳送不同類型的指令給服務工作站 (即「預先擷取」和「清除儲存空間」)。Service Worker 可根據這個標記,將分支版本分支至不同的執行路徑。

如果作業成功,使用者將可享受到好處,但如果未然,就無法變更主要的使用者流程。舉例來說,當 1-800-Flowers.com 嘗試預先快取時,網頁不需要知道 Service Worker 是否成功,如果能實現,使用者就可以享受更快速的瀏覽體驗。如果沒有顯示頁面,則仍需要前往新頁面。這需要再多一點時間。

簡單的預先擷取範例

「命令式快取」最常見的應用程式之一就是「預先擷取」,也就是先擷取特定網址的資源,再讓使用者前往該網址,藉此加快瀏覽速度。

您可以透過下列幾種方式在網站中執行預先擷取:

如果是相對簡單的預先擷取情境,例如預先擷取文件或特定資產 (JS、CSS 等),建議採用這些技巧。

如果需要其他邏輯,例如剖析預先擷取資源 (JSON 檔案或網頁) 以擷取其內部網址,更適合將這項工作完全委派給服務工作站。

將這些類型的作業委派給 Service Worker 有下列優點:

  • 將擷取及擷取後處理作業 (稍後將導入) 的繁瑣作業卸載至次要執行緒。如此一來,系統就會釋出主執行緒,處理更重要的工作,例如回應使用者互動。
  • 允許多個用戶端 (例如分頁) 重複使用通用功能,甚至在不封鎖主執行緒的情況下同時呼叫服務。

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

請先在 Service Worker 介面中使用 postMessage(),並將網址陣列傳送至快取:

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

在 Service Worker 中,實作 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
}

取得回應後,您可以仰賴資源的快取標頭。在許多情況下,和產品詳細資料頁面一樣,系統不會快取資源 (這代表資源具有 no-cacheCache-control 標頭)。在這種情況下,您可以將擷取的資源儲存在 Service Worker 快取中,覆寫這項行為。另一個好處是允許檔案在離線情境下提供。

不侷限於 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 預先擷取的好處是,這項作業可能會失敗,且不會導致網頁和主執行緒產生太多結果。在預先擷取的內容進行後續處理作業中,或許還能更精細地邏輯,以便讓資料更具彈性,並將處理的資料分離。盡情發揮,

結論

在本文中,我們探討了頁面與服務工作站之間的「單向」通訊常見用途:命令式快取。所討論的範例只適合展示一種模式的一種使用方式,相同的方法也適用於其他用途,例如隨選快取熱門文章,以便離線閱讀、加入書籤等等。

如要進一步瞭解網頁和服務 Worker 的通訊模式,請參閱:

  • 廣播更新:從 Service Worker 呼叫頁面,以通知重要更新資訊 (例如有可用的新版網頁應用程式)。
  • 雙向通訊:將工作委派給服務工作站 (例如大量下載),讓頁面持續掌握進度。