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

  1. Always use new: const { port1, port2 } = new MessageChannel().
  2. Wrap when passing as a callback: () => new MessageChannel().
  3. 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

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


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/4d41725b4c60822c. Report an issue: GitHub.