Automattic/mongoose · error · MongooseError
QueryCursor.prototype.eachAsync() no longer accepts a callba
Error message
QueryCursor.prototype.eachAsync() no longer accepts a callback
What it means
QueryCursor#eachAsync() no longer accepts a completion callback as its third argument in Mongoose 7+. Passing (fn, opts, cb) throws immediately; the method returns a Promise instead. The guard checks arguments[2] specifically — a two-argument (fn, cb) call does not throw, but the callback is silently dropped.
Source
Thrown at lib/cursor/queryCursor.js:367
* cursor().
* eachAsync(async function (doc, i) {
* doc.foo = doc.bar + i;
* await doc.save();
* })
*
* @param {Function} fn
* @param {object} [options]
* @param {number} [options.parallel] the number of promises to execute in parallel. Defaults to 1.
* @param {number} [options.batchSize] if set, will call `fn()` with arrays of documents with length at most `batchSize`
* @param {boolean} [options.continueOnError=false] if true, `eachAsync()` iterates through all docs even if `fn` throws an error. If false, `eachAsync()` throws an error immediately if the given function `fn()` throws an error.
* @return {Promise}
* @api public
* @method eachAsync
*/
QueryCursor.prototype.eachAsync = function(fn, opts) {
if (typeof arguments[2] === 'function') {
throw new MongooseError('QueryCursor.prototype.eachAsync() no longer accepts a callback');
}
if (typeof opts === 'function') {
opts = {};
}
opts = opts || {};
return eachAsync((cb) => _next(this, cb), fn, opts);
};
/**
* The `options` passed in to the `QueryCursor` constructor.
*
* @api public
* @property options
*/
QueryCursor.prototype.options;
View on GitHub (pinned to 49cdab0136)
Solutions
- Drop the callback and await: await queryCursor.eachAsync(fn, { parallel: 4 })
- Move error handling into a try/catch around the await
- To keep a callback interface outward, wrap: eachAsync(fn, opts).then(() => cb(null), cb)
Example fix
// before
Book.find().cursor().eachAsync(saveDoc, {}, function (err) {
if (err) return done(err);
done();
});
// after
try {
await Book.find().cursor().eachAsync(saveDoc);
done();
} catch (err) {
done(err);
} Defensive patterns
Strategy: validation
Validate before calling
function runEachAsync(cursor, fn, opts, cb) {
if (typeof cb === 'function') {
return cursor.eachAsync(fn, opts).then(() => cb(null), cb);
}
return cursor.eachAsync(fn, opts);
} Prevention
- Remember the exact trigger: only (fn, opts, cb) throws; (fn, cb) silently ignores the callback
- Standardize on await eachAsync(fn, opts) with try/catch
- Add migration tests that exercise each cursor helper after a Mongoose major upgrade
When it happens
Trigger: queryCursor.eachAsync(fn, {}, cb) or queryCursor.eachAsync(fn, { parallel: 2 }, function (err) {...}) — a function in the third argument slot.
Common situations: Streaming export/report scripts from the Mongoose 6 era; mixing options like parallel/batchSize with a legacy done-callback.
Related errors
- AggregationCursor.prototype.eachAsync() no longer accepts a
- QueryCursor.prototype.close() no longer accepts a callback
- QueryCursor.prototype.next() no longer accepts a callback
- AggregationCursor.prototype.close() no longer accepts a call
- AggregationCursor.prototype.next() no longer accepts a callb
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/5393217eff01716b.
Report an issue: GitHub.