在某些情況下,網頁應用程式可能需要在網頁和服務工作者之間建立雙向通訊管道。
舉例來說,在 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,我們將在下一節中說明:Message Channel 但介紹許多實作詳情,同時利用此 API 具備的全瀏覽器支援功能,更容易使用。
使用瀏覽器 API
如果 Workbox 程式庫無法滿足您的需求,您可以使用幾個較低層級的 API,在網頁和服務工作程式之間實作「雙向」通訊。兩者的相似之處和差異如下:
相似處:
- 在所有情況下,通訊都會透過
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...
}
};
如您所見,不會明確參照特定結構定義,因此不需要先取得服務工作站或任何特定用戶端的參照。
缺點是,在撰寫本文時,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
}
});
為了與任何用戶端通訊,Service Worker 會執行 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()
函式應傳回承諾,指出其嘗試執行的動作是否成功/失敗。如果傳回承諾,則表示同步處理已完成。如果失敗,系統會安排另一次同步作業來重試。重試同步作業也會等待連線,並採用指數輪詢。
作業執行完畢後,Service Worker 就可以使用先前探討的通訊 API 與頁面通訊,更新 UI。
Google 搜尋會使用背景同步功能,將因連線不良而失敗的查詢保留,並在使用者上線時重試。作業執行完畢後,他們會透過網頁推播通知將結果傳送給使用者:
背景擷取
對於傳送訊息或快取網址清單等較短的工作,目前探索的選項都是不錯的選擇。如果瀏覽器耗時太久,就會終止服務工作站,否則會影響使用者隱私和電池。
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}%`);
});
後續步驟
在本指南中,我們探討了頁面和服務工作者之間最常見的通訊方式 (雙向通訊)。
很多時候,一個人可能只需要一個情境就能與另一個人溝通,而不需要收到回應。請參閱下列指南,瞭解如何在網頁與 Service Worker 中實作單向技術,並提供用途和實際範例: