{"record":{"id":"5393217eff01716b","repo":"Automattic/mongoose","slug":"querycursor-prototype-eachasync-no-longer-accept","errorCode":null,"errorMessage":"QueryCursor.prototype.eachAsync() no longer accepts a callback","messagePattern":"QueryCursor\\.prototype\\.eachAsync\\(\\) no longer accepts a callback","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/cursor/queryCursor.js","lineNumber":367,"sourceCode":" *       cursor().\n *       eachAsync(async function (doc, i) {\n *         doc.foo = doc.bar + i;\n *         await doc.save();\n *       })\n *\n * @param {Function} fn\n * @param {object} [options]\n * @param {number} [options.parallel] the number of promises to execute in parallel. Defaults to 1.\n * @param {number} [options.batchSize] if set, will call `fn()` with arrays of documents with length at most `batchSize`\n * @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.\n * @return {Promise}\n * @api public\n * @method eachAsync\n */\n\nQueryCursor.prototype.eachAsync = function(fn, opts) {\n  if (typeof arguments[2] === 'function') {\n    throw new MongooseError('QueryCursor.prototype.eachAsync() no longer accepts a callback');\n  }\n  if (typeof opts === 'function') {\n    opts = {};\n  }\n  opts = opts || {};\n\n  return eachAsync((cb) => _next(this, cb), fn, opts);\n};\n\n/**\n * The `options` passed in to the `QueryCursor` constructor.\n *\n * @api public\n * @property options\n */\n\nQueryCursor.prototype.options;\n","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/cursor/queryCursor.js#L349-L385","documentation":"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.","triggerScenarios":"queryCursor.eachAsync(fn, {}, cb) or queryCursor.eachAsync(fn, { parallel: 2 }, function (err) {...}) — a function in the third argument slot.","commonSituations":"Streaming export/report scripts from the Mongoose 6 era; mixing options like parallel/batchSize with a legacy done-callback.","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)"],"exampleFix":"// before\nBook.find().cursor().eachAsync(saveDoc, {}, function (err) {\n  if (err) return done(err);\n  done();\n});\n\n// after\ntry {\n  await Book.find().cursor().eachAsync(saveDoc);\n  done();\n} catch (err) {\n  done(err);\n}","handlingStrategy":"validation","validationCode":"function runEachAsync(cursor, fn, opts, cb) {\n  if (typeof cb === 'function') {\n    return cursor.eachAsync(fn, opts).then(() => cb(null), cb);\n  }\n  return cursor.eachAsync(fn, opts);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["query-cursor","eachasync","callback","promise","mongoose-7","migration"],"backgroundTag":"callback-api-removed","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}