BroadcastChannel API - 网络的消息总线

Eric Bidelman

BroadcastChannel API 允许同源脚本向其他浏览上下文发送消息。可以将其视为简单的消息总线,允许在窗口/标签页、iframe、Web Worker 和 Service Worker 之间实现 Pub/Sub 语义。

API 基础知识

Broadcast Channel API 是一个简单的 API,可简化浏览上下文之间的通信。也就是说,在窗口/标签页、iframe、Web Worker 和 Service Worker 之间进行通信。发布到指定频道的消息将传送给该频道的所有监听器。

BroadcastChannel 构造函数只接受一个参数,即渠道名称。该名称用于标识频道,并且始终存在于各种浏览环境中。

// Connect to the channel named "my_bus".
const channel = new BroadcastChannel('my_bus');

// Send a message on "my_bus".
channel.postMessage('This is a test message.');

// Listen for messages on "my_bus".
channel.onmessage = function(e) {
    console.log('Received', e.data);
};

// Close the channel when you're done.
channel.close();

发送消息

消息可以是字符串,也可以是结构化克隆算法支持的任何内容(字符串、对象、数组、Blob、ArrayBuffer、映射)。

示例 - 发送 Blob 或 File

channel.postMessage(new Blob(['foo', 'bar'], {type: 'plain/text'}));

频道不会向自己广播。因此,如果与针对同一渠道的 postMessage() 在同一页面上有一个 onmessage 监听器,则系统不会触发 message 事件。

与其他技术的差异

此时,您可能想知道这与 WebSocket、SharedWorker、MessageChannel APIwindow.postMessage() 等其他消息传递技术有何关系。Broadcast Channel API 不会取代这些 API。每个选项都有自己的用途。Broadcast Channel API 旨在在同一源的脚本之间轻松进行一对多通信。

广播频道的一些用例:

  • 检测其他标签页中的用户操作
  • 了解用户何时在其他窗口/标签页中登录帐号。
  • 指示 worker 执行一些后台工作
  • 了解服务何时完成执行某些操作。
  • 当用户在一个窗口中上传照片时,将其传递给打开的其他页面。

示例 - 知道用户何时退出登录(即使是从同一网站上另一个已打开的标签页)的网页:

<button id="logout">Logout</button>

<script>
function doLogout() {
    // update the UI login state for this page.
}

const authChannel = new BroadcastChannel('auth');

const button = document.querySelector('#logout');
button.addEventListener('click', e => {
    // A channel won't broadcast to itself so we invoke doLogout()
    // manually on this page.
    doLogout();
    authChannel.postMessage({cmd: 'logout', user: 'Eric Bidelman'});
});

authChannel.onmessage = function(e) {
    if (e.data.cmd === 'logout') {
    doLogout();
    }
};
</script>

再举一个例子,假设您想要指示某个 Service Worker 在用户更改应用中的“离线存储设置”后移除缓存的内容。您可以使用 window.caches 删除其缓存,但 Service Worker 可能已包含一个可用于执行此操作的实用程序。我们可以通过广播通道 API 重复使用该代码!如果没有 Broadcast Channel API,您必须循环遍历 self.clients.matchAll() 的结果并对每个客户端调用 postMessage(),才能实现从 Service Worker 到其所有客户端的通信(实现这一目的的实际代码)。使用广播频道会使此 O(1) 而不是 O(N)

示例 - 指示 Service Worker 移除缓存,并重复使用其内部实用程序方法。

在 index.html 中

const channel = new BroadcastChannel('app-channel');
channel.onmessage = function(e) {
    if (e.data.action === 'clearcache') {
    console.log('Cache removed:', e.data.removed);
    }
};

const messageChannel = new MessageChannel();

// Send the service worker a message to clear the cache.
// We can't use a BroadcastChannel for this because the
// service worker may need to be woken up. MessageChannels do that.
navigator.serviceWorker.controller.postMessage({
    action: 'clearcache',
    cacheName: 'v1-cache'
}, [messageChannel.port2]);

在 sw.js 中

function nukeCache(cacheName) {
    return caches.delete(cacheName).then(removed => {
    // ...do more stuff (internal) to this service worker...
    return removed;
    });
}

self.onmessage = function(e) {
    const action = e.data.action;
    const cacheName = e.data.cacheName;

    if (action === 'clearcache') {
    nukeCache(cacheName).then(removed => {
        // Send the main page a response via the BroadcastChannel API.
        // We could also use e.ports[0].postMessage(), but the benefit
        // of responding with the BroadcastChannel API is that other
        // subscribers may be listening.
        const channel = new BroadcastChannel('app-channel');
        channel.postMessage({action, removed});
    });
    }
};

postMessage()的差异

postMessage() 不同,您不再需要保留对 iframe 或 worker 的引用,即可与其进行通信:

// Don't have to save references to window objects.
const popup = window.open('https://another-origin.com', ...);
popup.postMessage('Sup popup!', 'https://another-origin.com');

window.postMessage() 还可让您跨源进行通信。Broadcast Channel API 属于同源。由于消息一定会来自同一来源,因此没有必要像过去使用 window.postMessage() 那样验证消息:

// Don't have to validate the origin of a message.
const iframe = document.querySelector('iframe');
iframe.contentWindow.onmessage = function(e) {
    if (e.origin !== 'https://expected-origin.com') {
    return;
    }
    e.source.postMessage('Ack!', e.origin);
};

只需“订阅”特定频道即可进行安全的双向通信!

与 SharedWorker 的差异

在简单情况下,您可能需要向多个窗口/标签页或 worker 发送消息,请使用 BroadcastChannel

对于管理锁、共享状态、在服务器和多个客户端之间同步资源,或与远程主机共享 WebSocket 连接等复杂的用例,共享工作器是最合适的解决方案。

与 MessageChannel API 的差异

Channel Messaging APIBroadcastChannel 之间的主要区别在于,后者是向多个监听器(一对多)调度消息的方式。MessageChannel 适用于直接在脚本之间进行一对一通信。更复杂一些,要求您设置频道每端都有一个端口。

功能检测和浏览器支持

目前,Chrome 54、Firefox 38 和 Opera 41 支持广播通道 API。

if ('BroadcastChannel' in self) {
    // BroadcastChannel API supported!
}

至于 polyfill,有几个可用的选项:

我没有试过这些功能,因此你的里程数可能会有所不同。

资源