denoland/deno · error · DOMException
InvalidStateError
InvalidStateError
Error message
Already closed
What it means
BroadcastChannel.postMessage (ext/web/01_broadcast_channel.js:144) throws DOMException 'Already closed' with name InvalidStateError when the channel's closed flag is set. The flag is set by close() (and by the channel being removed), after which every postMessage is rejected before serialization. Once closed, the channel cannot be reopened.
Source
Thrown at ext/web/01_broadcast_channel.js:144
ArrayPrototypePush(channels, this);
refedBroadcastChannelsCount++;
if (rid === null) {
// Create the rid immediately, otherwise there is a time window (and a
// race condition) where messages can get lost, because recv() is async.
rid = op_broadcast_subscribe();
recv();
}
}
postMessage(message) {
webidl.assertBranded(this, BroadcastChannelPrototype);
const prefix = "Failed to execute 'postMessage' on 'BroadcastChannel'";
webidl.requiredArguments(arguments.length, 1, prefix);
if (this[_closed]) {
throw new DOMException("Already closed", "InvalidStateError");
}
if (typeof message === "function" || typeof message === "symbol") {
throw new DOMException("Uncloneable value", "DataCloneError");
}
// Serialize the message, carrying any SharedArrayBuffer backing stores
// out-of-band (referenced by `sabId`) so the message can be deserialized by
// an arbitrary number of receivers. `sabId` is 0 when there are none.
const { 0: data, 1: sabId } = op_broadcast_serialize(message, null);
// Send to other listeners in this VM.
dispatch(this, this[_name], data, sabId);
// Send to listeners in other VMs. This must happen before returning from
// postMessage(), otherwise close() immediately after postMessage() can
// cancel the deferred send before other workers observe the message.
op_broadcast_send(rid, this[_name], data, sabId);View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Check bc.closed before posting and skip or rethrow deliberately
- Centralize teardown: call close() only after all sends have finished (single shutdown function, await pending sends first)
- If a channel may have been closed, create a fresh BroadcastChannel(name) instead of reusing the instance
Example fix
// before
function shutdown() {
bc.close();
}
setInterval(() => bc.postMessage(tick()), 1000); // fires after shutdown -> throws
// after
let running = true;
function shutdown() {
running = false;
bc.close();
}
setInterval(() => {
if (!running || bc.closed) return;
bc.postMessage(tick());
}, 1000); Defensive patterns
Strategy: validation
Validate before calling
if (!bc || bc.closed) {
return; // channel already torn down
}
bc.postMessage(message); Type guard
function isOpen(bc: BroadcastChannel | null): bc is BroadcastChannel {
return bc !== null && !bc.closed;
} Try / catch
try {
bc.postMessage(message);
} catch (e) {
if (e instanceof DOMException && e.name === "InvalidStateError") {
// channel closed during shutdown; drop or re-create the channel
} else {
throw e;
}
} Prevention
- Check the closed property before every postMessage
- Own the lifecycle: one teardown path that closes the channel after pending sends drain
- Null out channel references after close() so use-after-close fails loudly
When it happens
Trigger: bc.postMessage(msg) after bc.close(); a finally block that closes the channel before an async send completes; two modules sharing a channel name where one closes its instance during shutdown while the other keeps sending.
Common situations: Shutdown/teardown ordering bugs in servers and workers; event handlers racing a close triggered by 'unload' or error paths; wrapper classes that auto-close on first error and then attempt to flush queued messages through the same channel.
Related errors
- DataCloneError
- Failed to construct 'QuotaExceededError': quota must not be
- Failed to construct 'QuotaExceededError': requested must not
- BenchContext::start() has already been invoked
- ${prefix}Linter plugin name must only contain lowercase lett
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/09e6bab78c241499.
Report an issue: GitHub.