denoland/deno · error · AbortError

ABORT_ERR

ABORT_ERR

Error message

The operation was aborted

What it means

events.once(emitter, name, { signal }) throws or rejects with AbortError (ABORT_ERR, 'The operation was aborted') when the AbortSignal passed in options is already aborted at call time (synchronous throw at _events.mjs:937), or when it aborts later, because the internal abortListener rejects the pending promise with the same error. This mirrors Node's cooperative-cancellation contract for the events API: aborting the signal cancels the wait for the event. It signals deliberate cancellation, not an emitter bug.

Source

Thrown at ext/node/polyfills/_events.mjs:937

    emitterOrTarget,
  );
}

/**
 * Creates a `Promise` that is fulfilled when the emitter
 * emits the given event.
 * @param {EventEmitter} emitter
 * @param {string} name
 * @param {{ signal: AbortSignal; }} [options]
 * @returns {Promise}
 */
// deno-lint-ignore require-await
async function once(emitter, name, options = kEmptyObject) {
  validateObject(options, "options");
  const signal = options?.signal;
  validateAbortSignal(signal, "options.signal");
  if (signal?.aborted) {
    throw new AbortError();
  }

  if (signal) {
    // Patches [kEvents] field of AbortSignal to simulate Node.js EventTarget
    // This is necessary to pass `paralle/test-events-once.js` test
    // TODO(kt3k): This can be removed if events.getEventListeners() is used
    // instead of `signal[kEvents]` in upstream.
    ObjectDefineProperty(signal, kEvents, kEventsGetter);
  }

  return new Promise((resolve, reject) => {
    const errorListener = (err) => {
      emitter.removeListener(name, resolver);
      if (signal != null) {
        eventTargetAgnosticRemoveListener(signal, "abort", abortListener);
      }
      reject(err);
    };

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Wrap the await in try/catch and treat code 'ABORT_ERR' as a normal cancellation path (return/ignore), rethrowing everything else
  2. Check signal?.aborted before calling events.once and skip the wait early
  3. Use a fresh AbortController per wait, or compose with AbortSignal.any() so unrelated aborts do not leak in
  4. If aborts are unexpected, find who calls abort() on the shared controller (timeout, shutdown hook, sibling consumer) and scope the signal more narrowly

Example fix

// before
const [msg] = await events.once(emitter, 'ready', { signal: controller.signal });

// after
try {
  const [msg] = await events.once(emitter, 'ready', { signal: controller.signal });
} catch (err) {
  if (err.code === 'ABORT_ERR') return; // cancelled on purpose
  throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

import events from 'node:events';

function notAborted(signal) {
  return !(signal && signal.aborted);
}

if (notAborted(controller.signal)) {
  await events.once(emitter, 'ready', { signal: controller.signal });
}

Type guard

const isAbortError = (err) =>
  err != null &&
  (err.code === 'ABORT_ERR' || err.name === 'AbortError');

Try / catch

try {
  const [value] = await events.once(emitter, name, { signal });
} catch (err) {
  if (err.code === 'ABORT_ERR') {
    return; // deliberate cancellation: stop cleanly
  }
  throw err; // real 'error' event or bug: propagate
}

Prevention

When it happens

Trigger: Calling events.once(emitter, 'ready', { signal }) with signal.aborted === true; calling controller.abort() (directly or via AbortController.timeout / AbortSignal.timeout) while awaiting events.once; sharing one AbortController across several once() waits so one completion path aborts the others; a shutdown handler aborting a global signal that a pending once() still uses.

Common situations: Implementing request timeouts with AbortController; racing a timeout signal against a server 'ready' event; reusing a signal from an already-cancelled fetch; process-shutdown listeners aborting in-flight waits; version changes where a dependency started passing signals into events.once.

Related errors


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