API BroadcastChannel – Một bus thông báo cho web

Eric Bidelman

BroadcastChannel API cho phép các tập lệnh có cùng nguồn gốc gửi thông báo đến các ngữ cảnh duyệt web khác. Có thể coi đây là một bus thông báo đơn giản cho phép ngữ nghĩa pub/sub giữa các cửa sổ/thẻ, iframe, trình chạy web và trình chạy dịch vụ.

Kiến thức cơ bản về API

Broadcast Channel API (API kênh phát sóng) là một API đơn giản giúp việc giao tiếp giữa các ngữ cảnh duyệt web dễ dàng hơn. Tức là giao tiếp giữa các cửa sổ/thẻ, iframe, trình chạy web và trình chạy dịch vụ. Tin nhắn được đăng lên một kênh cụ thể sẽ được gửi đến tất cả người nghe của kênh đó.

Hàm khởi tạo BroadcastChannel nhận một tham số duy nhất: tên của một kênh. Tên này xác định kênh và xuất hiện trên các ngữ cảnh duyệt web.

// 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();

Gửi tin nhắn

Thông báo có thể là chuỗi hoặc bất kỳ nội dung nào được thuật toán sao chép có cấu trúc hỗ trợ (Chuỗi, Đối tượng, Mảng, Blobs, ArrayBuffer, Bản đồ).

Ví dụ – gửi một tệp Blob hoặc tệp

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

Một kênh sẽ không phát sóng cho chính nó. Vì vậy, nếu bạn có trình nghe onmessage trên cùng một trang với postMessage() đến cùng một kênh, thì sự kiện message đó sẽ không kích hoạt.

Điểm khác biệt với các kỹ thuật khác

Tại thời điểm này, bạn có thể thắc mắc về mối liên hệ giữa điều này với các kỹ thuật khác để truyền thông báo như WebSockets, SharedWorkers, API MessageChannelwindow.postMessage(). Broadcast Channel API (API Kênh phát sóng) không thay thế các API này. Mỗi nhóm đều phục vụ một mục đích. Broadcast Channel API giúp bạn dễ dàng giao tiếp một với nhiều giữa các tập lệnh trên cùng một nguồn gốc.

Một số trường hợp sử dụng kênh phát sóng:

  • Phát hiện thao tác của người dùng trong các thẻ khác
  • Biết khi nào người dùng đăng nhập vào tài khoản trong một cửa sổ/thẻ khác.
  • Hướng dẫn nhân viên thực hiện một số công việc ở chế độ nền
  • Biết thời điểm một dịch vụ thực hiện xong một hành động nào đó.
  • Khi người dùng tải ảnh lên trong một cửa sổ, hãy chuyển ảnh đó sang các trang đang mở khác.

Ví dụ – trang biết thời điểm người dùng đăng xuất, thậm chí là từ một thẻ đang mở khác trên cùng trang web:

<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>

Trong một ví dụ khác, giả sử bạn muốn hướng dẫn một trình chạy dịch vụ xoá nội dung đã lưu vào bộ nhớ đệm sau khi người dùng thay đổi "chế độ cài đặt bộ nhớ ngoại tuyến" trong ứng dụng của bạn. Bạn có thể xoá bộ nhớ đệm của họ bằng cách sử dụng window.caches, nhưng trình chạy dịch vụ đó có thể đã chứa tiện ích để làm việc này. Chúng ta có thể sử dụng Broadcast Channel API để sử dụng lại mã đó! Nếu không có Broadcast Channel API, bạn phải lặp lại kết quả của self.clients.matchAll() và gọi postMessage() trên từng ứng dụng để có thể giao tiếp giữa một trình chạy dịch vụ với tất cả các ứng dụng của mình (mã thực tế thực hiện điều đó). Việc sử dụng Kênh truyền phát sẽ tạo O(1) này thay vì O(N).

Ví dụ – hướng dẫn trình chạy dịch vụ xoá bộ nhớ đệm, sử dụng lại các phương thức tiện ích nội bộ.

Trong 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]);

Trong 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});
    });
    }
};

Chênh lệch với postMessage()

Không giống như postMessage(), bạn không còn phải duy trì tham chiếu đến iframe hoặc trình thực thi để giao tiếp với iframe đó:

// 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() cũng cho phép bạn giao tiếp giữa các nguồn gốc. API kênh phát sóng có cùng nguồn gốc. Vì các thư được đảm bảo đến từ cùng một nguồn gốc, nên bạn không cần phải xác thực các thư đó như chúng ta đã từng làm với 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);
};

Chỉ cần "đăng ký" kênh cụ thể và giao tiếp hai chiều một cách bảo mật!

Điểm khác biệt với SharedWorkers

Sử dụng BroadcastChannel cho các trường hợp đơn giản khi bạn cần gửi tin nhắn đến nhiều cửa sổ/thẻ hoặc trình thực thi.

Đối với những trường hợp sử dụng hấp dẫn hơn như quản lý khoá, trạng thái dùng chung, đồng bộ hoá tài nguyên giữa một máy chủ và nhiều ứng dụng hoặc chia sẻ kết nối WebSocket với một máy chủ từ xa, thì trình thực thi dùng chung là giải pháp phù hợp nhất.

Sự khác biệt với MessageChannel API

Điểm khác biệt chính giữa Channel Messaging APIBroadcastChannel là API này là phương tiện gửi thông báo đến nhiều trình nghe (một với nhiều). MessageChannel được dùng để giao tiếp trực tiếp giữa các tập lệnh. Việc này cũng phức tạp hơn, yêu cầu bạn phải thiết lập các kênh với một cổng ở mỗi đầu.

Phát hiện tính năng và hỗ trợ trình duyệt

Hiện tại, Chrome 54, Firefox 38 và Opera 41 hỗ trợ Broadcast Channel API.

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

Đối với polyfill, có một số loại như sau:

Tôi chưa thử những đề xuất này nên quãng đường bạn đã đi có thể thay đổi.

Tài nguyên