javascripttypescriptruntime-errorw3cbroadcast-channel

BroadcastChannel API inconsistently throws error if it was closed when calling postMessage()


When working with BroadcastChannel API, I realized that there's a case where if the channel is closed and the developer still try to call postMessage(), there won't be any exception thrown; at least not all the time.

First case

close() is called from the same broadcastChannel instance with postMessage(), error is thrown:

const bc1 = new BroadcastChannel('foo');
const bc2 = new BroadcastChannel('foo');

bc1.postMessage('bar');

bc2.addEventListener('message', (event) => console.log(event.data));

setTimeout(() => {
  bc1.close(); // we're calling the close() from bc1, same instant where we will call postMessage later
}, 500);

setTimeout(() => {
  try {
    bc1.postMessage('bar2');
  } catch (error) {
    console.error('postMessage error', error);
  }
}, 1000);

The code above will throw an exception:

postMessage error
Error: Failed to execute 'postMessage' on 'BroadcastChannel': Channel is closed

Second case

close() is called from the a different broadcastChannel instance with postMessage(), error is NOT thrown:

const bc1 = new BroadcastChannel('foo');
const bc2 = new BroadcastChannel('foo');

bc1.postMessage('bar');

bc2.addEventListener('message', (event) => console.log(event.data));

setTimeout(() => {
  bc2.close(); // we're calling the close() from bc2, NOT the same instant where we will call postMessage later
}, 500);

setTimeout(() => {
  try {
    bc1.postMessage('bar2');
  } catch (error) {
    console.error('postMessage error', error);
  }
}, 1000);

The code above doesn't throw any exception. IMO, it should have thrown an exception instead.

Question

Is there any possible way to detect whether the channel has been closed in the second case? Or if this's a bug, where can I file a ticket?

If you want to play with the MRE, here's the link.


Solution

  • After reading the documentation, I think it is an acceptable behaviour. First of all, here is a Note from the official specification:

    Authors are strongly encouraged to explicitly close BroadcastChannel objects when they are no longer needed so that they can be garbage collected. Creating many BroadcastChannel objects and discarding them while leaving them with an event listener and without closing them can lead to an apparent memory leak, since the objects will continue to live for as long as they have an event listener (or until their page or worker is closed).

    Apparently, we need to clean up BroadcastChannel instances to prevent memory leaks. And now, thinking further, if we broadcast messages to different contexts for syncing/exchanging information, we need to create instances there with the same channel name. However, other contexts could be easily closed which induces the clearance of resources for that particular context. Nevertheless, other contexts should continue exchanging messages - the show must go on.

    To sum up, I think it is totally fine to not throw an exception because BroadCastChannel is not aware of all its instances. To overcome the problem, you may create a close handler function that will be placed in each BroadcastChannel listener. For ex:

    bChannel.onmessage = function (event) {
      if (event.data == 'close'){
        // clear something...
        bChannel.close();
      }
    }
    

    And then you can call bChannel.postMessage('close'); which will close all instances (set a close flag to false). After that, they should throw errors.