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

  1. Drop the callback and await: await queryCursor.eachAsync(fn, { parallel: 4 })
  2. Move error handling into a try/catch around the await
  3. 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

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


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/5393217eff01716b. Report an issue: GitHub.