某些網站可能需要連線至 Service Worker,不需要通知結果。例子如下:
將這些類型的非重要工作委派給服務工作站,有助於釋出主執行緒,能更妥善地處理較緊迫的任務,例如回應使用者互動。
在本指南中,我們將探討如何使用標準瀏覽器 API 和 Workbox 程式庫,實作從網頁到服務工作站的「單向」通訊技術。我們將這些類型的用途稱為「命令式快取」。
正式環境案例
1-800-Flowers.com 透過 postMessage()
將命令式快取 (預先擷取) 與服務工作站搭配使用,預先擷取類別頁面中的熱門項目,以加速使用者後續瀏覽產品詳細資料頁面。
他們會運用混合的方法決定要預先擷取的項目:
- 網頁載入時間時,他們會要求服務工作站擷取前 9 個項目的 JSON 資料,並將產生的回應物件新增至快取。
- 其餘項目則會監聽
mouseover
事件,因此當使用者將遊標移至項目上方時,就可以依「隨選」觸發資源的擷取作業。
這些會使用 Cache API 來儲存 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 是否成功,如果能實現,使用者就可以享受更快速的瀏覽體驗。如果沒有顯示頁面,則仍需要前往新頁面。這需要再多一點時間。
簡單的預先擷取範例
「命令式快取」最常見的應用程式之一就是「預先擷取」,也就是先擷取特定網址的資源,再讓使用者前往該網址,藉此加快瀏覽速度。
您可以透過下列幾種方式在網站中執行預先擷取:
- 在網頁中使用連結預先擷取標記:資源會在瀏覽器快取中保留五分鐘,之後資源就會套用一般的
Cache-Control
規則。 - 以服務工作站中的執行階段快取策略補強上述技術,將預先擷取資源的生命週期延長超過此上限。
如果是相對簡單的預先擷取情境,例如預先擷取文件或特定資產 (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-cache
的 Cache-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 的通訊模式,請參閱: