denoland/deno · error · NodeTypeError

ERR_INVALID_ARG_TYPE

ERR_INVALID_ARG_TYPE

Error message

The "signal" argument must be an instance of AbortSignal. Received undefined

What it means

events.addAbortListener(signal, listener) attaches a one-shot 'abort' listener to an AbortSignal and returns a Disposable. The polyfill rejects an undefined signal up front with ERR_INVALID_ARG_TYPE (message says 'must be an instance of AbortSignal. Received undefined') before validateAbortSignal and validateFunction run. Any other non-AbortSignal value fails the subsequent validateAbortSignal check instead.

Source

Thrown at ext/node/polyfills/internal/events/abort_listener.mjs:19

// Copyright 2018-2026 the Deno authors. MIT license.
// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
(function () {
const { core, primordials } = __bootstrap;
const { queueMicrotask, SymbolDispose } = primordials;
const { validateAbortSignal, validateFunction } = core.loadExtScript(
  "ext:deno_node/internal/validators.mjs",
);
const { codes } = core.loadExtScript("ext:deno_node/internal/errors.ts");
const { ERR_INVALID_ARG_TYPE } = codes;

/**
 * @param {AbortSignal} signal
 * @param {EventListener} listener
 * @returns {Disposable}
 */
function addAbortListener(signal, listener) {
  if (signal === undefined) {
    throw new ERR_INVALID_ARG_TYPE("signal", "AbortSignal", signal);
  }
  validateAbortSignal(signal, "signal");
  validateFunction(listener, "listener");

  let removeEventListener;
  if (signal.aborted) {
    queueMicrotask(() => listener({ target: signal }));
  } else {
    signal.addEventListener("abort", listener, {
      __proto__: null,
      once: true,
    });
    removeEventListener = () => {
      signal.removeEventListener("abort", listener);
    };
  }
  return {
    __proto__: null,

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Create and pass a real signal: const { signal } = new AbortController(); events.addAbortListener(signal, onAbort)
  2. Fix argument order: (signal, listener), not (listener, signal)
  3. Guard optional plumbing: if (signal) events.addAbortListener(signal, listener)
  4. Type parameters as AbortSignal so the compiler rejects undefined call sites

Example fix

// before
events.addAbortListener(onAbort, options.signal); // swapped; signal undefined

// after
if (options.signal) events.addAbortListener(options.signal, onAbort);
Defensive patterns

Strategy: validation

Validate before calling

function onAbort(signal, listener) {
  if (!(signal instanceof AbortSignal)) throw new TypeError('signal must be an AbortSignal');
  if (typeof listener !== 'function') throw new TypeError('listener must be a function');
  return events.addAbortListener(signal, listener);
}

Type guard

const isAbortSignal = (v) => v instanceof AbortSignal;

Try / catch

try { events.addAbortListener(signal, listener); } catch (e) { if (e?.code === 'ERR_INVALID_ARG_TYPE' && /signal/.test(e.message)) throw new TypeError('addAbortListener requires (signal: AbortSignal, listener)'); throw e; }

Prevention

When it happens

Trigger: events.addAbortListener() with no arguments; swapping the argument order: addAbortListener(onAbort, signal); plumbing options.signal that was never set: addAbortListener(options?.signal, fn); destructuring { signal } from an undefined options object.

Common situations: AbortController created in one module but forgotten when calling the helper; wrapper APIs that mirror options-object style while addAbortListener is positional; TypeScript without strictNullChecks letting undefined through as an AbortSignal.

Related errors


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