在某些情況下,網頁應用程式可能需要在網頁和 Service Worker 之間建立雙向通訊管道。
舉例來說,在 Podcast PWA 中,開發人員可以建構功能,讓使用者下載集數以供離線收聽,並允許服務工作人員定期將進度通知頁面,以便主要執行緒更新 UI。
在本指南中,我們將探討在 Window 和服務工作人員環境之間實作雙向通訊的不同方式,包括探索不同 API、Workbox 程式庫,以及一些進階案例。

使用 Workbox
workbox-window
是一組 Workbox 程式庫模組,可在視窗環境中執行。Workbox
類別提供 messageSW()
方法,可將訊息傳送至執行個體已註冊的 Service Worker,並等待回應。
下列網頁程式碼會建立新的 Workbox
例項,並將訊息傳送至 Service Worker,以取得其版本:
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 (我們將在下一節中查看:MessageChannel),但會抽象化許多實作詳細資料,讓您更輕鬆地使用,同時利用這個 API 廣泛的瀏覽器支援。

使用瀏覽器 API
如果 Workbox 程式庫無法滿足您的需求,您可以使用多種低階 API,在網頁和 Service Worker 之間實作「雙向」通訊。兩者有異同之處:
相似處:
- 無論是哪種情況,通訊都會從一端透過
postMessage()
介面啟動,並在另一端透過實作message
處理常式接收。 - 實際上,所有可用的 API 都允許我們實作相同的使用案例,但其中有些 API 可能會在某些情況下簡化開發作業。
差異:
- 這些方法識別通訊另一端的方式不同:有些方法會明確參照其他內容,有些則可透過在每一端例項化的 Proxy 物件隱含通訊。
- 各項功能支援的瀏覽器不盡相同。

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...
}
};
如您所見,這裡沒有明確參照特定內容,因此不需要先取得服務工作人員或任何特定用戶端的參照。

缺點是撰寫本文時,這項 API 僅支援 Chrome、Firefox 和 Edge,其他瀏覽器 (例如 Safari) 尚未支援。
Client API
用戶端 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
物件,並使用該物件將連接埠傳送至已註冊的 Service Worker。這個頁面也會在 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()
應傳回 Promise,指出嘗試執行的任何作業是否成功。如果 Promise 順利完成,表示同步完成。如果失敗,系統會排定另一次同步作業,重試同步作業時,系統也會等待連線,並採用指數輪詢。
作業完成後,服務工作站就能使用先前探討的任何通訊 API,與網頁通訊並更新 UI。
如果連線品質不佳導致查詢失敗,Google 搜尋會使用 Background Sync 持續嘗試,並在使用者連線後重試。作業完成後,系統會透過網頁推播通知將結果傳達給使用者:

背景擷取
對於相對較短的工作 (例如傳送訊息或要快取的網址清單),目前探討的選項是不錯的選擇。如果工作耗時過長,瀏覽器會終止 Service Worker,否則會對使用者的隱私權和電池造成風險。
Background Fetch API 可將長時間執行的工作卸載至 Service Worker,例如下載電影、Podcast 或遊戲關卡。
如要從網頁與 Service Worker 通訊,請使用 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}%`);
});

後續步驟
在本指南中,我們探討了網頁與 Service Worker 之間最常見的通訊案例 (雙向通訊)。
很多時候,您可能只需要一個內容來與另一個內容通訊,不需要收到回應。請參閱下列指南,瞭解如何在網頁中從服務工作人員傳送資料,以及傳送資料至服務工作人員,並查看相關用途和實際工作環境範例: