denoland/deno · error · TypeError
ERR_CONSTRUCT_CALL_REQUIRED
ERR_CONSTRUCT_CALL_REQUIRED
Error message
Class constructor MessageChannel cannot be invoked without `new`
What it means
worker_threads' MessageChannel is deliberately implemented as a function whose body throws Node's ERR_CONSTRUCT_CALL_REQUIRED('MessageChannel') when invoked without new — reproducing Node's error code and constructor identity instead of V8's generic class-constructor message. The node_compat suite asserts both the code and the constructor for the called-without-new and normal forms.
Source
Thrown at ext/node/polyfills/worker_threads.ts:1878
}
port[MessagePortReceiveMessageOnPortSymbol] = true;
const portId = getMessagePortId(port);
if (portId === null) return undefined;
const data = op_message_port_recv_message_sync(portId);
if (data === null) return undefined;
const message = deserializeJsMessageData(data)[0];
patchMessagePortIfFound(message);
return { message };
}
// Implemented as a function (not a class) so calling without `new` throws
// our Node-style `ERR_CONSTRUCT_CALL_REQUIRED` instead of V8's default
// "Class constructor X cannot be invoked without 'new'" -- the node_compat
// suite asserts the specific code/constructor for both forms.
// deno-lint-ignore no-explicit-any
function NodeMessageChannel(this: any) {
if (new.target === undefined) {
throw new ERR_CONSTRUCT_CALL_REQUIRED("MessageChannel");
}
{
const channel = new MessageChannel();
const port1 = webMessagePortToNodeMessagePort(channel.port1);
const port2 = webMessagePortToNodeMessagePort(channel.port2);
// The web MessageChannel.prototype defines port1/port2 as getter-only
// accessors, so a plain `this.port1 = ...` triggers a setter trap. Use
// defineProperty to shadow the prototype accessors with own data
// properties that hold our Node-style ports.
ObjectDefineProperty(this, "port1", {
__proto__: null,
value: port1,
writable: true,
enumerable: true,
configurable: true,
});
ObjectDefineProperty(this, "port2", {
__proto__: null,View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Always use new: const { port1, port2 } = new MessageChannel().
- Wrap when passing as a callback: () => new MessageChannel().
- Enable TypeScript — calling a class-typed constructor without new is a compile error.
Example fix
// before
const channel = MessageChannel();
// after
const channel = new MessageChannel();
const { port1, port2 } = channel; Defensive patterns
Strategy: try-catch
Validate before calling
function makeChannel(): MessageChannel {
return new MessageChannel(); // single construction path, always with new
}
const { port1, port2 } = makeChannel(); Try / catch
let channel: MessageChannel;
try {
channel = (MessageChannel as unknown as () => MessageChannel)();
} catch (e) {
if (e?.code === 'ERR_CONSTRUCT_CALL_REQUIRED') channel = new MessageChannel();
else throw e;
} Prevention
- Route all channel creation through one factory that uses new.
- Let TypeScript flag bad call sites — constructor types reject the call form.
- Code-review .map/.from callbacks that receive constructors directly.
When it happens
Trigger: MessageChannel(); MessageChannel.call({}); MessageChannel.apply(null, []); passing MessageChannel directly as a callback (arr.map(MessageChannel)) so it is invoked unbound.
Common situations: Older factory-style code; transpilers or bundlers rewriting classes; passing constructors as callbacks; snippets predating ES classes.
Related errors
- ERR_CONSTRUCT_CALL_REQUIRED
- ERR_INSPECTOR_NOT_WORKER
- ERR_WORKER_UNSUPPORTED_OPERATION
- ERR_CONSTRUCT_CALL_REQUIRED
- ERR_ILLEGAL_CONSTRUCTOR
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/4d41725b4c60822c.
Report an issue: GitHub.