caolan/async · error · Error

Callback was already called.

Error message

Callback was already called.

What it means

onlyOnce wraps a callback so it can only be invoked a single time; a second invocation throws 'Callback was already called.' This protects control-flow helpers (each, whilst, queue, etc.) from double-completion, which would otherwise corrupt results or call your final callback twice.

Source

Thrown at lib/internal/onlyOnce.js:3

export default function onlyOnce(fn) {
    return function (...args) {
        if (fn === null) throw new Error("Callback was already called.");
        var callFn = fn;
        fn = null;
        callFn.apply(this, args);
    };
}

View on GitHub (pinned to 13dfaf13f3)

Solutions

  1. Ensure each callback path calls cb exactly once and returns immediately after
  2. Guard with a done flag or use async's once utility around your own logic
  3. Don't mix promises and callbacks on the same completion path
  4. Check error branches: after cb(err) add return so code doesn't continue to cb(null)

Example fix

// before
fs.readFile(path, (err, data) => {
  if (err) cb(err);
  cb(null, data); // called twice on error
});
// after
fs.readFile(path, (err, data) => {
  if (err) return cb(err);
  cb(null, data);
});
Defensive patterns

Strategy: try-catch

Validate before calling

function wrapSingleCall(cb) {
  let called = false;
  return (...args) => {
    if (called) throw new Error('Callback was already called.');
    called = true;
    return cb(...args);
  };
}

Try / catch

iteratee = (item, cb) => {
  const onceCb = once((err) => result(err));
  try {
    doWork(item, onceCb);
  } catch (err) {
    onceCb(err);
  }
};

Prevention

When it happens

Trigger: Calling the done/callback parameter twice in an iteratee or worker — e.g. calling cb(err) inside a try/catch and then calling cb again, or invoking cb both in a promise .then and .catch, or calling the outer callback and an internal one.

Common situations: Mixing promise resolution with manual callback invocation, fire-and-forget async calls that later invoke the shared callback, error paths that fall through to a second cb call, using the same callback for multiple async operations.

Related errors


AI-assisted analysis of caolan/async@13dfaf13f3 (2026-08-28). Data as JSON: /api/errors/aa55334163180272. Report an issue: GitHub.