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

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,我們會在下一節中介紹:訊息管道,但會抽象化許多實作細節,讓使用者更容易使用,同時利用此 API 提供的廣泛瀏覽器支援

圖表顯示使用 Workbox Window 時,網頁和服務工作程式之間的雙向通訊。

使用瀏覽器 API

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

相似處:

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

差異:

  • 它們有不同的方式來識別通訊的另一端:有些會明確參照其他內容,有些則會透過在兩端例項化代理物件,間接進行通訊。
  • 瀏覽器支援的情況因瀏覽器而異。
這張圖表顯示網頁與服務工作者之間的雙向通訊,以及可用的瀏覽器 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
  }
});

為了與任何用戶端進行回傳通訊,服務工作者會透過執行 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.
  • Edge: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
};
圖表顯示網頁將通訊埠傳遞至服務工作者,以建立雙向通訊。

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

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() 函式應傳回承諾,指出其嘗試執行的動作是否成功/失敗。如果傳回成功,則表示同步處理已完成。如果失敗,系統會安排另一次同步作業來重試。重試同步作業也會等待連線,並採用指數輪詢。

作業完成後,服務工作站可以使用先前介紹的任何通訊 API,與頁面進行通訊,以便更新 UI。

Google 搜尋會使用背景同步功能,將因連線不良而失敗的查詢保留,並在使用者上線時重試。作業完成後,系統會透過網路推播通知向使用者傳達結果:

圖表顯示網頁將通訊埠傳遞至服務工作者,以建立雙向通訊。

背景擷取

瀏覽器支援

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

資料來源

對於傳送訊息或快取網址清單等較短的工作,目前探索的選項都是不錯的選擇。如果工作耗時過長,瀏覽器會終止服務 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}%`);
});
圖表顯示網頁將通訊埠傳遞至服務工作者,以建立雙向通訊。
使用者介面已更新,以顯示下載進度 (左圖)。多虧了服務工作程式,在所有分頁都關閉後,作業仍可繼續執行 (右圖)。

後續步驟

在本指南中,我們探討了頁面和服務工作者之間最常見的通訊方式 (雙向通訊)。

很多時候,一個人可能只需要一個情境就能與另一個人溝通,而不需要收到回應。請參閱下列指南,瞭解如何在頁面中實作服務工作程的單向技術,以及相關用途和實際應用範例:

  • 強制快取指南:從頁面呼叫 Service Worker,以便預先快取資源 (例如在預先載入情境中)。
  • 廣播更新:從服務工作者呼叫網頁,以便通知重要更新 (例如推出新版的 webapp)。