部分網站可能需要與服務工作站通訊,但不需要收到結果通知。例如:
- 網頁會將網址清單傳送給 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"]}}); });
Service Worker 會實作 message
處理常式來監聽這些訊息。您可以選擇傳回回應,但在下列情況下,這並非必要:
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'PREFETCH') {
// do something
}
});
使用瀏覽器 API
如果 Workbox 程式庫無法滿足您的需求,以下說明如何使用瀏覽器 API 實作視窗,以服務工作站通訊。
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 檔案或頁面),以便擷取內部網址),建議您將這項工作完全委派給服務工作員。
將這類作業委派給服務工作者有以下優點:
- 將擷取和後擷取處理的繁重工作卸載至次要執行緒。如此一來,主執行緒就能釋放處理更重要的工作,例如回應使用者互動。
- 讓多個用戶端 (例如分頁) 重複使用常見功能,甚至在不封鎖主執行緒的情況下同時呼叫服務。
預先擷取產品詳細資料頁面
首先,在 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',
],
},
});
在服務工作站中,實作 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-cache
的 Cache-control
標頭)。在這類情況下,您可以將擷取的資源儲存在服務工作站快取中,以覆寫這個行為。這項做法還有另一項好處,就是可在離線情境中提供檔案。
不只 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 之間單向通訊的常見用途:強制快取。我們討論的範例只是展示使用這個模式的一種方式,同樣做法也適用於其他用途,例如將熱門文章快取供離線使用、書籤等。
如要進一步瞭解頁面和服務工作單元通訊的其他模式,請參閱: