在某些情況下,網頁應用程式可能需要在網頁和服務工作者之間建立雙向通訊管道。
舉例來說,在 Podcast PWA 中,您可以建構一項功能,讓使用者下載各集內容以供離線收聽,並讓服務工作者定期向網頁提供進度資訊,以便主執行緒更新 UI。
在本指南中,我們將探討實作 Window 和 service worker 之間雙向通訊的不同方式,方法是探索不同的 API、Workbox 程式庫,以及一些進階案例。
使用 Workbox
workbox-window
是 Workbox 程式庫的一組模組,可在視窗內容中執行。Workbox
類別提供 messageSW()
方法,可將訊息傳送至例項註冊的服務工作者,並等待回應。
下列頁面程式碼會建立新的 Workbox
例項,並傳送訊息至服務工作者,以取得其版本:
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
服務工作站會在另一端實作訊息事件監聽器,並回應已註冊的服務工作站:
const SW_VERSION = '1.0.0';
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
程式庫在幕後使用瀏覽器 API,我們會在下一節中介紹:訊息管道,但會抽象化許多實作細節,讓使用者更容易使用,同時利用此 API 提供的廣泛瀏覽器支援。
使用瀏覽器 API
如果 Workbox 程式庫無法滿足您的需求,您可以使用幾個較低層級的 API,在網頁和服務工作程式之間實作「雙向」通訊。兩者之間有一些相似之處和差異:
相似處:
- 在所有情況下,通訊都會透過
postMessage()
介面從一端開始,並透過實作message
處理常式在另一端接收。 - 實際上,所有可用的 API 都能實作相同的用途,但在某些情況下,部分 API 可能會簡化開發作業。
差異:
- 它們有不同的方式來識別通訊的另一端:有些會明確參照其他內容,有些則會透過在兩端例項化代理物件,間接進行通訊。
- 瀏覽器支援的情況因瀏覽器而異。
Broadcast Channel API
Broadcast Channel API 可透過 BroadcastChannel 物件,在瀏覽內容之間進行基本通訊。
如要實作這項功能,首先,每個內容都必須以相同的 ID 例項化 BroadcastChannel
物件,並透過該物件傳送及接收訊息:
const broadcast = new BroadcastChannel('channel-123');
BroadcastChannel 物件會公開 postMessage()
介面,用於將訊息傳送至任何監聽情境:
//send message
broadcast.postMessage({ type: 'MSG_ID', });
任何瀏覽器情境都可以透過 BroadcastChannel
物件的 onmessage
方法監聽訊息:
//listen to messages
broadcast.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process message...
}
};
如您所見,這裡並未明確參照特定情境,因此無須先取得服務工作者或任何特定用戶端的參照。
缺點是,在撰寫本文時,Chrome、Firefox 和 Edge 支援這項 API,但 Safari 等其他瀏覽器尚未支援。
用戶端 API
Client API 可讓您取得所有 WindowClient
物件的參照,這些物件代表服務工作者控制的有效分頁。
由於網頁由單一 Service Worker 控管,因此會透過 serviceWorker
介面直接收聽並傳送訊息至有效的 Service Worker:
//send message
navigator.serviceWorker.controller.postMessage({
type: 'MSG_ID',
});
//listen to messages
navigator.serviceWorker.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process response
}
};
同樣地,服務工作站會透過實作 onmessage
事件監聽器來監聽訊息:
//listen to messages
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//Process message
}
});
為了與任何用戶端進行回傳通訊,服務工作者會透過執行 Clients.matchAll()
和 Clients.get()
等方法,取得 WindowClient
物件的陣列。接著,它可以postMessage()
任何一個:
//Obtain an array of Window client objects
self.clients.matchAll(options).then(function (clients) {
if (clients && clients.length) {
//Respond to last focused tab
clients[0].postMessage({type: 'MSG_ID'});
}
});
Client API
是個不錯的選項,可讓您以相對簡單的方式,輕鬆與服務工作員的所有有效分頁進行通訊。所有主要瀏覽器都支援這個 API,但可能無法使用其所有方法,因此請務必在網站中導入前先確認瀏覽器支援。
訊息管道
訊息管道需要定義並從一個內容傳遞至另一個內容的通訊端口,才能建立雙向通訊管道。
為了初始化管道,網頁會將 MessageChannel
物件例項化,並使用該物件將端口傳送至已註冊的服務工作站。這個頁面也會實作 onmessage
事件監聽器,以便接收來自其他情境的訊息:
const messageChannel = new MessageChannel();
//Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
messageChannel.port2,
]);
//Listen to messages
messageChannel.port1.onmessage = (event) => {
// Process message
};
服務工作站會接收通訊埠、儲存對應的參照,並使用該參照傳送訊息至另一端:
let communicationPort;
//Save reference to port
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'PORT_INITIALIZATION') {
communicationPort = event.ports[0];
}
});
//Send messages
communicationPort.postMessage({type: 'MSG_ID'});
MessageChannel
目前支援所有主要瀏覽器。
進階 API:背景同步處理和背景擷取
在本指南中,我們探討了實作雙向通訊技巧的方法,適用於相對簡單的情況,例如傳遞描述要執行的作業的字串訊息,或是從一個情境快取至另一個情境的網址清單。在本節中,我們將探討兩個 API 來處理特定情況:缺乏連線和下載時間過長。
背景同步處理
聊天應用程式可能會想確保訊息不會因連線不良而遺失。Background Sync API 可讓您在使用者連線穩定時,延後重試動作。這有助於確保使用者要傳送的內容確實傳送。
頁面註冊 sync
,而非 postMessage()
介面:
navigator.serviceWorker.ready.then(function (swRegistration) {
return swRegistration.sync.register('myFirstSync');
});
服務工作站接著會監聽 sync
事件,以便處理訊息:
self.addEventListener('sync', function (event) {
if (event.tag == 'myFirstSync') {
event.waitUntil(doSomeStuff());
}
});
doSomeStuff()
函式應傳回承諾,指出其嘗試執行的動作是否成功/失敗。如果傳回成功,則表示同步處理已完成。如果失敗,系統會安排另一次同步作業來重試。重試同步作業也會等待連線,並採用指數輪詢。
作業完成後,服務工作站可以使用先前介紹的任何通訊 API,與頁面進行通訊,以便更新 UI。
Google 搜尋會使用背景同步功能,將因連線不良而失敗的查詢保留,並在使用者上線時重試。作業完成後,系統會透過網路推播通知向使用者傳達結果:
背景擷取
對於傳送訊息或快取網址清單等較短的工作,目前探索的選項都是不錯的選擇。如果工作耗時過長,瀏覽器會終止服務 worker,否則可能會危害使用者的隱私權和電池。
Background Fetch API 可讓您將長時間的作業卸載至服務工作站,例如下載電影、Podcast 或遊戲關卡。
如要透過網頁與服務工作站通訊,請使用 backgroundFetch.fetch
,而非 postMessage()
:
navigator.serviceWorker.ready.then(async (swReg) => {
const bgFetch = await swReg.backgroundFetch.fetch(
'my-fetch',
['/ep-5.mp3', 'ep-5-artwork.jpg'],
{
title: 'Episode 5: Interesting things.',
icons: [
{
sizes: '300x300',
src: '/ep-5-icon.png',
type: 'image/png',
},
],
downloadTotal: 60 * 1024 * 1024,
},
);
});
BackgroundFetchRegistration
物件可讓網頁監聽 progress
事件,追蹤下載進度:
bgFetch.addEventListener('progress', () => {
// If we didn't provide a total, we can't provide a %.
if (!bgFetch.downloadTotal) return;
const percent = Math.round(
(bgFetch.downloaded / bgFetch.downloadTotal) * 100,
);
console.log(`Download progress: ${percent}%`);
});
後續步驟
在本指南中,我們探討了頁面和服務工作者之間最常見的通訊方式 (雙向通訊)。
很多時候,一個人可能只需要一個情境就能與另一個人溝通,而不需要收到回應。請參閱下列指南,瞭解如何在頁面中實作服務工作程的單向技術,以及相關用途和實際應用範例:
- Imperative 快取指南:從頁面呼叫 Service Worker,以便預先快取資源 (例如在預先載入情境中)。
- 廣播更新:從服務工作者呼叫網頁,以便通知重要更新 (例如推出新版的 webapp)。