與服務工作人員之間的雙向通訊

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

在某些情況下,網頁應用程式可能需要在網頁和服務工作者之間建立雙向通訊管道。

舉例來說,在 Podcast PWA 中,您可以建構讓使用者下載節目供離線使用的功能,並允許服務工作處理程序定期接收網頁最新資訊,讓主執行緒能夠更新 UI。

在本指南中,我們將探討實作 Windowservice worker 之間雙向通訊的不同方式,方法是探索不同的 API、Workbox 程式庫,以及一些進階案例。

顯示 Service Worker 和網頁交換訊息的圖表。

使用 Workbox

workbox-windowWorkbox 程式庫的一組模組,可在視窗內容中執行。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 具備的全瀏覽器支援功能,更容易使用。

這張圖表顯示使用 Workbox 視窗,頁面與服務工作處理程序之間進行雙向通訊。

使用瀏覽器 API

如果 Workbox 程式庫無法滿足您的需求,您可以使用幾個較低層級的 API,在網頁和服務工作程式之間實作「雙向」通訊。兩者的相似之處和差異如下:

相似處:

  • 在所有情況下,通訊都會透過 postMessage() 介面從一端開始,並透過實作 message 處理常式在另一端接收。
  • 實際上,所有可用的 API 都能實作相同的用途,但在某些情況下,部分 API 可能會簡化開發作業。

差異:

  • 它們有不同的方式來識別通訊的另一端:有些會明確參照其他內容,有些則會透過在兩端例項化的 Proxy 物件間接通訊。
  • 瀏覽器支援會因瀏覽器而異。
這張圖表顯示頁面和 Service Worker,以及可用的瀏覽器 API 之間的雙向通訊。

Broadcast Channel API

瀏覽器支援

  • Chrome:54。
  • Edge:79,
  • Firefox:38。
  • Safari:15.4。

資料來源

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

瀏覽器支援

  • Chrome:40。
  • Edge:17。
  • Firefox:44。
  • Safari:11.1。

資料來源

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,但可能無法使用其所有方法,因此請務必在網站中導入前確認瀏覽器支援。

訊息管道

瀏覽器支援

  • Chrome:2.
  • 邊緣:12。
  • Firefox:41。
  • Safari:5.

資料來源

訊息管道需要定義並從一個內容傳遞至另一個內容的通訊端口,才能建立雙向通訊管道。

為了初始化管道,網頁會將 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
};
圖表顯示一個頁面將通訊埠傳遞至 Service Worker,用來建立雙向通訊的頁面。

服務工作站會接收通訊埠、儲存對應的參照,並使用該參照傳送訊息至另一端:

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 來處理特定情況:連線能力不足和下載時間過長。

背景同步處理

瀏覽器支援

  • Chrome:49。
  • Edge:79。
  • Firefox:不支援。
  • Safari:不支援。

資料來源

聊天應用程式可能會想確保訊息不會因為連線不良而遺失。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 搜尋會使用背景同步功能,將因連線不良而失敗的查詢保留,並在使用者上線時重試。作業執行完畢後,他們會透過網頁推播通知將結果傳送給使用者:

圖表顯示一個頁面將通訊埠傳遞至 Service Worker,用來建立雙向通訊的頁面。

背景擷取

瀏覽器支援

  • Chrome:74。
  • Edge:79。
  • Firefox:不支援。
  • Safari:不支援。

資料來源

對於傳送訊息或快取網址清單等較短的工作,目前探索的選項都是不錯的選擇。如果瀏覽器耗時太久,就會終止服務工作站,否則會影響使用者隱私和電池。

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 中實作單向技術,並提供用途和實際範例:

  • 強制快取指南:從頁面呼叫 Service Worker,以便預先快取資源 (例如在預先載入情境中)。
  • 廣播更新:從 Service Worker 呼叫頁面,通知重要更新 (例如有新版網頁應用程式可供使用)。