部分網站可能需要與服務工作站通訊,但不需要收到結果通知。例如:
- 網頁會將網址清單傳送給 Service Worker,用於預先快取,這樣當使用者點選連結時,文件或網頁子資源就會已在快取中,讓後續導覽更快速。
- 這個頁面會要求 Service Worker 擷取並快取一組熱門文章,以便離線使用。
將這類非重要工作委派給服務工作站,可讓主執行緒釋出,以便更妥善地處理更緊急的工作,例如回應使用者互動。
在本指南中,我們將探討如何使用標準瀏覽器 API 和 Workbox 程式庫,實作從網頁到服務工作者的單向通訊技巧。我們將這類用途稱為強制快取。
實際工作環境案例
1-800-Flowers.com 透過 postMessage()
使用服務工作者實作強制快取 (預先載入),預先載入分類頁面中的熱門商品,加快後續前往產品詳細資料頁面的速度。
它們會使用混合方法決定要預先載入哪些項目:
- 在頁面載入期間,他們會要求服務器 worker 擷取前 9 項項目的 JSON 資料,並將產生的回應物件加入快取。
- 至於其他項目,則會監聽
mouseover
事件,這樣當使用者將游標移至項目上方時,就能觸發「需求」的資源擷取作業。
他們使用 Cache API 來儲存 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 嘗試預快取時,網頁不需要知道服務工作者是否成功。如果是這樣,使用者就能享有更快速的導覽體驗。如果不是,頁面仍需要導向新頁面。只是需要多一點時間。
簡單的預先載入範例
強制快取最常見的應用之一是預先擷取,也就是在使用者前往特定網址之前,先擷取該網址的資源,以便加快導覽速度。
在網站中實作預先載入功能的方法有很多種:
- 在網頁中使用連結預先擷取標記:資源會保留在瀏覽器快取中五分鐘,之後會套用資源的一般
Cache-Control
規則。 - 搭配服務 worker 中的執行階段快取策略,補足先前技術,將預先擷取資源的生命週期延長至超出此限制。
對於較為簡單的預先載入情境,例如預先載入文件或特定資產 (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 預先載入的優點是,即使失敗,對網頁和主執行緒的影響也不大。您也可以在預先擷取內容的後置處理程序中加入更複雜的邏輯,讓後置處理程序更具彈性,並與處理的資料解耦。創意無極限。
結論
在本文中,我們將介紹網頁與服務工作單元之間單向通訊的常見用途:強制快取。我們討論的範例只是展示使用這個模式的一種方式,同樣做法也適用於其他用途,例如根據需求快取熱門文章,以便離線使用、加入書籤等。
如要進一步瞭解頁面和服務工作單元通訊的其他模式,請參閱: