如何与 Service Worker 通信
如何与 Service Worker 通信
疯狂的技术宅 前端先锋
翻译:疯狂的技术宅
作者:Felix Gerschau
来源:felixgerschau.com
正文共:2534 字
预计阅读时间:10 分钟
Service Worker 很棒。它们使 Web 开发人员可以实现以前原生应用专有的类似功能。这类功能是例如推送通知或后台同步的离线功能。
它们是渐进式 Web 应用的核心。但是在设置它们之后,似乎很难完成涉及与 Web 应用交互的更复杂的事情。
在本文中,我将展示可用的选择并最后进行比较。
Service Worker 与 Web Worker
如果你查看 Service Workers 的 API,将会看到 Web Worker 和 Service Worker 有非常相似的接口。尽管有相似之处,但它们的意图和功能却大不相同:
Service Worker vs Web Worker
- Service Worker 可以拦截请求并将其替换为自己缓存中的项目,因此它们的行为就像是代理服务器。他们为 Web 应用提供了“离线功能”。
它们可以在多个标签中使用,甚至在所有标签关闭后仍然可以使用。
- 另一方面,Web worker 有不同的用途。它们为单线程 JavaScript 语言提供了多线程功能,并用于执行计算繁重的任务,这些任务不应干扰 UI 的响应能力。
它们仅限于一个标签 。
两者的共同点是它们无权访问 DOM,无法使用 postMessage API 进行通信。你可以将它们看作是具有扩展功能的 Web Worker。
如果你想了解有关它们更多信息,请查看这个对话,尽管有些陈旧,但可以个很好的概述这个话题。到 2020 年,[Service Workers 的浏览器支持](https://caniuse.com/#search=service worker)有了很大的改进。
如何与 Service Worker 通信
选择要向其发送消息的 Service Worker
对于任何来源,都可以有多个 Service Worker。以下内容返回当前控制页面的活动 Service Worker:
1navigator.serviceWorker.controller
如果要访问其他 Service Worker,则可以通过 registration 接口访问,该借口使你可以访问以下位置的 Service Worker 状态:
- ServiceWorkerRegistration.installing
- ServiceWorkerRegistration.waiting - 已安装此 Service Worker,但尚未激活
- ServiceWorkerRegistration.active -此Service Worker正在控制当前页面
你可以通过几种不同的方式访问 registration 接口。其中有一个 navigator.serviceWorker.ready。它将返回一个可以通过注册解决的 promise:
1navigator.serviceWorker.ready.then((registration) => { 2 // At this point, a Service Worker is controlling the current page 3});
如果你想了解有关 Service Worker 生命周期的更多信息,请查看这篇文章:(https://bitsofco.de/the-service-worker-lifecycle/)。
发送信息
正如我已经提到的,Service Worker 通过 postMessage API 进行通信。这不仅允许他们与JavaScript主线程交换数据,而且还可以将消息从一个Service Worker发送到另一个Service Worker。
1// app.js - Somewhere in your web app 2navigator.serviceWorker.controller.postMessage({ 3 type: 'MESSAGE_IDENTIFIER', 4}); 5// service-worker.js 6// On the Service Worker side we have to listen to the message event 7self.addEventListener('message', (event) => { 8 if (event.data && event.data.type === 'MESSAGE_IDENTIFIER') { 9 // do something 10 } 11});
这种单向通信的用例是在等待服务的 Service Worker 中调用 skipWaiting,然后将其传递为活动状态并控制页面。这已在 Create-React-App 附带的 Service Worker 中实现。我用此技术在渐进式 Web 应用中显示更新通知,我在这篇文章(https://felixgerschau.com/create-a-pwa-update-notification-with-create-react-app)中进行了解释。
但是如果你想将消息发送回 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个步骤。
- 在两侧设置事件侦听器以接收 'message' 事件
- 通过发送 port 并将其存储在 Service Worker 中,建立与 Service Worker 的连接。
- 使用存储的 port 回复客户端
也可以添加第四步,如果你想通过在 Service Worker 中调用 port.close() 来关闭连接的话。
在实践中看起来像这样:
1// app.js - somewhere in our main app 2const messageChannel = new MessageChannel(); 3 4// First we initialize the channel by sending 5// the port to the Service Worker (this also 6// transfers the ownership of the port) 7navigator.serviceWorker.controller.postMessage({ 8 type: 'INIT_PORT', 9}, [messageChannel.port2]); 10 11// Listen to the response 12messageChannel.port1.onmessage = (event) => { 13 // Print the result 14 console.log(event.data.payload); 15}; 16 17// Then we send our first message 18navigator.serviceWorker.controller.postMessage({ 19 type: 'INCREASE_COUNT', 20}); 21// service-worker.js 22let getVersionPort; 23let count = 0; 24self.addEventListener("message", event => { 25 if (event.data && event.data.type === 'INIT_PORT') { 26 getVersionPort = event.ports[0]; 27 } 28 29 if (event.data && event.data.type === 'INCREASE_COUNT') { 30 getVersionPort.postMessage({ payload: ++count }); 31 } 32}
使用 Broadcast API
Broadcast API 与 MessageChannel 非常相似,但是它消除了将端口传递给 Service Worker 的需求。
在这个例子中,我们看到只需要在两侧建立一个有相同名称 count-channel 的通道。
我们可以将相同的代码添加到其他 WebWorker 或 Service Worker,后者也将接收所有这些消息。
在这里,我们从上方看到了相同的例子,但用了 Broadcast API:
1// app.js 2// Set up channel 3const broadcast = new BroadcastChannel('count-channel'); 4 5// Listen to the response 6broadcast.onmessage = (event) => { 7 console.log(event.data.payload); 8}; 9 10// Send first request 11broadcast.postMessage({ 12 type: 'INCREASE_COUNT', 13}); 14// service-worker.js 15// Set up channel with same name as in app.js 16const broadcast = new BroadcastChannel('count-channel'); 17broadcast.onmessage = (event) => { 18 if (event.data && event.data.type === 'INCREASE_COUNT') { 19 broadcast.postMessage({ payload: ++count }); 20 } 21};
使用 Client API
Client API 也不需要传递对通道的引用。
在客户端,我们侦听 Service Worker 的响应,在 Service Worker 中,用 self.clients.matchAll 函数提供给我们的过滤器选项,选择要发送响应的客户端。
1// app.js 2// Listen to the response 3navigator.serviceWorker.onmessage = (event) => { 4 if (event.data && event.data.type === 'REPLY_COUNT_CLIENTS') { 5 setCount(event.data.count); 6 } 7}; 8 9// Send first request 10navigator.serviceWorker.controller.postMessage({ 11 type: 'INCREASE_COUNT_CLIENTS', 12}); 13// service-worker.js 14// Listen to the request 15self.addEventListener('message', (event) => { 16 if (event.data && event.data.type === 'INCREASE_COUNT') { 17 // Select who we want to respond to 18 self.clients.matchAll({ 19 includeUncontrolled: true, 20 type: 'window', 21 }).then((clients) => { 22 if (clients && clients.length) { 23 // Send a response - the clients 24 // array is ordered by last focused 25 clients[0].postMessage({ 26 type: 'REPLY_COUNT', 27 count: ++count, 28 }); 29 } 30 }); 31 } 32});
总结
postMessage API提供了一个简单灵活的接口,使我们可以将消息发送给 Service Worker。
Broadcast Channel API 是最容易使用的选项,但不幸的是,它的浏览器支持并不是很好。
在剩下的两个中,我更喜欢 Client API,因为这不需要将引用传递给 Service Worker。
原文链接
https://felixgerschau.com/how-to-communicate-with-service-workers
- 渐进式web应用开发---Service Worker 与页面通信(七)
- Service 之间如何通信?- 每天5分钟玩转 Docker 容器技术(101)
- 如何实现跨应用绑定Service的通信
- 如何正确地卸载Service Worker?
- JavaScript是如何工作的:Service Worker的生命周期及使用场景
- activity和service之间如何进行通信?
- 【面试】Service与Activity如何实现通信
- 【面试】Service与Activity如何实现通信
- Service 之间如何通信?- 每天5分钟玩转 Docker 容器技术(101)
- Service 之间如何通信?- 每天5分钟玩转 Docker 容器技术(101)
- Activity如何使用Handler与Service通信
- Service 之间如何通信?- 每天5分钟玩转 Docker 容器技术(101)
- .net与java webService通信的最简单方法
- 如何使用 volatile, synchronized, final 进行线程间通信
- Android开发学习之Service详解三,Android跨进程通信
- Android 音乐播放器的开发教程(七)运用Broadcast实现service与activity的通信 ----- 小达
- 我的WCF之旅(7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承
- [原创]WCF技术剖析之二十三:服务实例(Service Instance)生命周期如何控制[第1篇]
- Android中Activity、Service和线程之间的通信
- [原创]WCF后续之旅(1): WCF是如何通过Binding进行通信的