denoland/deno · error · TypeError
MessageEvent constructor: Expected eventInitDict.ports[${i}]
Error message
MessageEvent constructor: Expected eventInitDict.ports[${i}] (${formatForBranded(p)}) to be an instance of MessagePort. What it means
After the iterability check, MessageEvent's constructor iterates eventInitDict.ports and verifies every element against the web MessagePort prototype (ext/web/02_event.js:1366-1377). An element that is null, a primitive, or not branded as a MessagePort throws TypeError 'Expected eventInitDict.ports[i] ... to be an instance of MessagePort' with the offending index. Deno iterates using the user's own iterator so exotic iterables still work — only the element type is enforced.
Source
Thrown at ext/web/02_event.js:1374
formatForRaw(ports)
}) is not iterable.`,
);
}
const MessagePortProto = getMessagePortPrototype();
const arr = [];
let i = 0;
// Iterate using the user's own iterator so values that aren't real
// arrays (e.g. a `RegExp` with a custom `Symbol.iterator` -- covered
// by WPT's no-regexp-special-casing test) still produce the
// expected `ports` array. SafeArrayIterator can't be used here
// because it walks the value as if it were an Array.
// deno-lint-ignore deno-internal/prefer-primordials
for (const p of ports) {
if (
p === null || typeof p !== "object" ||
!ObjectPrototypeIsPrototypeOf(MessagePortProto, p)
) {
throw new TypeError(
`MessageEvent constructor: Expected eventInitDict.ports[${i}] (${
formatForBranded(p)
}) to be an instance of MessagePort.`,
);
}
arr[i++] = p;
}
// `ports` is a FrozenArray<MessagePort> per the HTML spec.
this.ports = ObjectFreeze(arr);
}
// origin and lastEventId are USVString per spec, so coerce to string
// (Node's test passes numbers and expects string coercion).
this.origin = eventInitDict?.origin === undefined
? ""
: String(eventInitDict.origin);
this.lastEventId = eventInitDict?.lastEventId === undefined
? ""
: String(eventInitDict.lastEventId);View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Filter the array to real web MessagePorts before constructing: ports.filter((p) => p instanceof MessagePort).
- Pass only ports obtained from MessageChannel or a worker/web-worker onmessage event — never node:worker_threads ports.
- Remove null/undefined placeholders instead of leaving holes in ports.
Example fix
// before
new MessageEvent('message', { data, ports: [port1, null] });
// TypeError: Expected eventInitDict.ports[1] (null) to be an instance of MessagePort.
// after
new MessageEvent('message', { data, ports: [port1] }); Defensive patterns
Strategy: type-guard
Validate before calling
const ports = rawPorts.filter((p) => p instanceof MessagePort);
new MessageEvent('message', { data, ports }); Type guard
const areAllMessagePorts = (a) => Array.isArray(a) && a.every((p) => p instanceof MessagePort);
Prevention
- Filter null/undefined sentinels out of ports arrays
- Never mix node:worker_threads ports into web MessageEvent
- Validate ports at the boundary where worker messages enter your code
When it happens
Trigger: new MessageEvent('message', { data, ports: [new MessageChannel().port1, null] }); arrays containing a node:worker_threads port, a number, a string, or a plain object; sparse arrays with holes (undefined elements).
Common situations: Port arrays built with placeholders/sentinels (null) for closed or unused channels; mixing Node worker_threads MessagePort objects into web MessageEvent construction; arrays produced by concat/map that accidentally inject undefined.
Related errors
- MessageEvent constructor: eventInitDict.ports (${formatForRa
- MessageEvent constructor: Expected eventInitDict.source (${f
- BenchContext::start() has already been invoked
- ${prefix}Linter plugin name must only contain lowercase lett
- ${prefix}Linter plugin name must start and end with a lowerc
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/517d75e0da2ea43f.
Report an issue: GitHub.