如何与 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。


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎继续阅读本专栏其它高赞文章:


相关文章
相关标签/搜索