作者:Felix Gerschau翻译:疯狂的技术宅
原文:https://felixgerschau.com/how...
未经允许严禁转载
Service Worker 很棒。它们使 Web 开发人员可以实现以前原生应用专有的类似功能。这类功能是例如推送通知或后台同步的离线功能。
它们是渐进式 Web 应用的核心。但是在设置它们之后,似乎很难完成涉及与 Web 应用交互的更复杂的事情。
在本文中,我将展示可用的选择并最后进行比较。
Service Worker 与 Web Worker
如果你查看 Service Workers 的 API,将会看到 Web Worker 和 Service Worker 有非常相似的接口。尽管有相似之处,但它们的意图和功能却大不相同:
- 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个步骤。
- 在两侧设置事件侦听器以接收 'message' 事件
- 通过发送 port 并将其存储在 Service Worker 中,建立与 Service Worker 的连接。
- 使用存储的 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。
本文首发微信公众号:前端先锋
欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章
欢迎继续阅读本专栏其它高赞文章:
- 深入理解Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13个帮你提高开发效率的现代CSS框架
- 快速上手BootstrapVue
- JavaScript引擎是如何工作的?从调用栈到Promise你需要知道的一切
- WebSocket实战:在 Node 和 React 之间进行实时通信
- 关于 Git 的 20 个面试题
- 深入解析 Node.js 的 console.log
- Node.js 究竟是什么?
- 30分钟用Node.js构建一个API服务器
- Javascript的对象拷贝
- 程序员30岁前月薪达不到30K,该何去何从
- 14个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩展插件
- Node.js 多线程完全指南
- 把HTML转成PDF的4个方案及实现