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
- Ensure each callback path calls cb exactly once and returns immediately after
- Guard with a done flag or use async's once utility around your own logic
- Don't mix promises and callbacks on the same completion path
- 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
- Return immediately after every cb(...) call, especially in error branches
- Never mix promise resolution and manual callback invocation on one path
- Audit iteratees for code paths that can reach cb twice
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
- async.auto task `${key}` has a non-existent dependency `${de
- async.auto cannot execute tasks due to a recursive dependenc
- could not parse args in autoInject Source: ${src}
- autoInject task functions require explicit parameters.
- arity is undefined
AI-assisted analysis of caolan/async@13dfaf13f3 (2026-08-28).
Data as JSON: /api/errors/aa55334163180272.
Report an issue: GitHub.