如何與 Service Worker 通訊

做者:Felix Gerschau

翻譯:瘋狂的技術宅前端

原文:https://felixgerschau.com/how...react

未經容許嚴禁轉載程序員

Service Worker 很棒。它們使 Web 開發人員能夠實現之前原生應用專有的相似功能。這類功能是例如推送通知後臺同步的離線功能。web

它們是漸進式 Web 應用的核心。可是在設置它們以後,彷佛很難完成涉及與 Web 應用交互的更復雜的事情。面試

在本文中,我將展現可用的選擇並最後進行比較。segmentfault

Service Worker 與 Web Worker

若是你查看 Service Workers 的 API,將會看到 Web Worker 和 Service Worker 有很是類似的接口。儘管有類似之處,但它們的意圖和功能卻大不相同:promise

image.png

  • Service Worker 能夠攔截請求並將其替換爲本身緩存中的項目,所以它們的行爲就像是代理服務器。他們爲 Web 應用提供了「離線功能」。

它們能夠在多個標籤中使用,甚至在全部標籤關閉後仍然可使用。瀏覽器

  • 另外一方面,Web worker 有不一樣的用途。它們爲單線程 JavaScript 語言提供了多線程功能,並用於執行計算繁重的任務,這些任務不該干擾 UI 的響應能力。

它們僅限於一個標籤 緩存

二者的共同點是它們無權訪問 DOM,沒法使用 postMessage API 進行通訊。你能夠將它們看做是具備擴展功能的 Web Worker。服務器

若是你想了解有關它們更多信息,請查看這個對話,儘管有些陳舊,但能夠個很好的概述這個話題。到 2020 年,Service Workers 的瀏覽器支持有了很大的改進。

如何與 Service Worker 通訊

選擇要向其發送消息的 Service Worker

對於任何來源,均可以有多個 Service Worker。如下內容返回當前控制頁面的活動 Service Worker:

navigator.serviceWorker.controller

若是要訪問其餘 Service Worker,則能夠經過 registration 接口訪問,該藉口使你能夠訪問如下位置的 Service Worker 狀態:

  • ServiceWorkerRegistration.installing
  • ServiceWorkerRegistration.waiting - 已安裝此 Service Worker,但還沒有激活
  • ServiceWorkerRegistration.active -此Service Worker正在控制當前頁面

你能夠經過幾種不一樣的方式訪問 registration 接口。其中有一個 navigator.serviceWorker.ready。它將返回一個能夠經過註冊解決的 promise:

navigator.serviceWorker.ready.then((registration) => {
  // At this point, a Service Worker is controlling the current page
});

若是你想了解有關 Service Worker 生命週期的更多信息,請查看這篇文章:(https://bitsofco.de/the-servi...)。

發送信息

正如我已經提到的,Service Worker 經過 postMessage API 進行通訊。這不只容許他們與JavaScript主線程交換數據,並且還能夠將消息從一個Service Worker發送到另外一個Service Worker。

// app.js - Somewhere in your web app
navigator.serviceWorker.controller.postMessage({
  type: 'MESSAGE_IDENTIFIER',
});
// service-worker.js
// On the Service Worker side we have to listen to the message event
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'MESSAGE_IDENTIFIER') {
    // do something
  }
});

這種單向通訊的用例是在等待服務的 Service Worker 中調用 skipWaiting,而後將其傳遞爲活動狀態並控制頁面。這已在 Create-React-App 附帶的 Service Worker 中實現。我用此技術在漸進式 Web 應用中顯示更新通知,我在這篇文章(https://felixgerschau.com/cre...)中進行了解釋。

可是若是你想將消息發送回 Window上下文甚至其餘 Service Worker,該怎麼辦?

Service Worker - Client 通訊

有好幾種方法能夠將消息發送到 Service Worker 的客戶端:

  • Broadcast Channel API 容許瀏覽上下文之間進行通訊。此 API 容許上下文之間進行通訊,而無需引用。Chrome、Firefox 和 Opera 目前支持該功能。可以創建多對多廣播通訊。
  • MessageChannel API 它可用於在 Window 和 Service Worker 上下文之間創建一對一通訊。
  • Service Worker 的 Clients 接口。它可用於向 Service Worker 的一個或多個客戶端進行廣播。

我將爲你提供每一個方法的簡短示例,而後將它們進行比較,以查看哪一種方法最適合你的用例。

我沒有包含 FetchEvent.respondWith(),由於這僅適用於獲取事件,並且目前不受 Safari 瀏覽器支持。

使用 MessageChannel API

顧名思義,MessageChannel API 設置了一個能夠發送消息的通道。

該實現能夠歸結爲3個步驟。

  1. 在兩側設置事件偵聽器以接收 'message' 事件
  2. 經過發送 port 並將其存儲在 Service Worker 中,創建與 Service Worker 的鏈接。
  3. 使用存儲的 port 回覆客戶端

也能夠添加第四步,若是你想經過在 Service Worker 中調用 port.close() 來關閉鏈接的話。

在實踐中看起來像這樣:

// app.js - somewhere in our main app
const messageChannel = new MessageChannel();

// First we initialize the channel by sending
// the port to the Service Worker (this also
// transfers the ownership of the port)
navigator.serviceWorker.controller.postMessage({
  type: 'INIT_PORT',
}, [messageChannel.port2]);

// Listen to the response
messageChannel.port1.onmessage = (event) => {
  // Print the result
  console.log(event.data.payload);
};

// Then we send our first message
navigator.serviceWorker.controller.postMessage({
  type: 'INCREASE_COUNT',
});
// service-worker.js
let getVersionPort;
let count = 0;
self.addEventListener("message", event => {
  if (event.data && event.data.type === 'INIT_PORT') {
    getVersionPort = event.ports[0];
  }

  if (event.data && event.data.type === 'INCREASE_COUNT') {
    getVersionPort.postMessage({ payload: ++count });
  }
}

使用 Broadcast API

Broadcast API 與 MessageChannel 很是類似,可是它消除了將端口傳遞給 Service Worker 的需求。

在這個例子中,咱們看到只須要在兩側創建一個有相同名稱 count-channel 的通道。

咱們能夠將相同的代碼添加到其餘 WebWorker 或 Service Worker,後者也將接收全部這些消息。

在這裏,咱們從上方看到了相同的例子,但用了 Broadcast API:

// app.js
// Set up channel
const broadcast = new BroadcastChannel('count-channel');

// Listen to the response
broadcast.onmessage = (event) => {
  console.log(event.data.payload);
};

// Send first request
broadcast.postMessage({
  type: 'INCREASE_COUNT',
});
// service-worker.js
// Set up channel with same name as in app.js
const broadcast = new BroadcastChannel('count-channel');
broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'INCREASE_COUNT') {
    broadcast.postMessage({ payload: ++count });
  }
};

使用 Client API

Client API 也不須要傳遞對通道的引用。

在客戶端,咱們偵聽 Service Worker 的響應,在 Service Worker 中,用 self.clients.matchAll 函數提供給咱們的過濾器選項,選擇要發送響應的客戶端。

// app.js
// Listen to the response
navigator.serviceWorker.onmessage = (event) => {
  if (event.data && event.data.type === 'REPLY_COUNT_CLIENTS') {
    setCount(event.data.count);
  }
};

// Send first request
navigator.serviceWorker.controller.postMessage({
  type: 'INCREASE_COUNT_CLIENTS',
});
// service-worker.js
// Listen to the request
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'INCREASE_COUNT') {
    // Select who we want to respond to
    self.clients.matchAll({
      includeUncontrolled: true,
      type: 'window',
    }).then((clients) => {
      if (clients && clients.length) {
        // Send a response - the clients
        // array is ordered by last focused
        clients[0].postMessage({
          type: 'REPLY_COUNT',
          count: ++count,
        });
      }
    });
  }
});

總結

postMessage API提供了一個簡單靈活的接口,使咱們能夠將消息發送給 Service Worker。

Broadcast Channel API 是最容易使用的選項,但不幸的是,它的瀏覽器支持並非很好。

在剩下的兩個中,我更喜歡 Client API,由於這不須要將引用傳遞給 Service Worker。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索