{"record":{"id":"457b1242a1c40b54","repo":"Automattic/mongoose","slug":"mongoose-does-not-support-using-async-iterators-wi","errorCode":null,"errorMessage":"Mongoose does not support using async iterators with an existing aggregation cursor. See https://bit.ly/mongoose-async-iterate-aggregation","messagePattern":"Mongoose does not support using async iterators with an existing aggregation cursor\\. See https://bit\\.ly/mongoose-async-iterate-aggregation","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/cursor/aggregationCursor.js","lineNumber":166,"sourceCode":"    if (!doc) {\n      _this.push(null);\n      _this.cursor.close(function(error) {\n        if (error) {\n          return _this.emit('error', error);\n        }\n      });\n      return;\n    }\n    _this.push(doc);\n  });\n};\n\nif (Symbol.asyncIterator != null) {\n  const msg = 'Mongoose does not support using async iterators with an ' +\n    'existing aggregation cursor. See https://bit.ly/mongoose-async-iterate-aggregation';\n\n  AggregationCursor.prototype[Symbol.asyncIterator] = function() {\n    throw new MongooseError(msg);\n  };\n}\n\n/**\n * Registers a transform function which subsequently maps documents retrieved\n * via the streams interface or `.next()`\n *\n * #### Example:\n *\n *     // Map documents returned by `data` events\n *     Thing.\n *       find({ name: /^hello/ }).\n *       cursor().\n *       map(function (doc) {\n *        doc.foo = \"bar\";\n *        return doc;\n *       })\n *       on('data', function(doc) { console.log(doc.foo); });","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/cursor/aggregationCursor.js#L148-L184","documentation":"Mongoose throws this when Symbol.asyncIterator (for await...of) is used on an aggregation cursor that was already executed via .exec(). Async iteration of Mongoose cursors relies on re-running the underlying operation when iteration starts, and an already-executed aggregation cannot be transparently re-created, so Mongoose refuses instead of failing silently later.","triggerScenarios":"const cursor = Model.aggregate(pipeline).cursor().exec(); for await (const doc of cursor) {...} — any for await...of loop over an existing AggregationCursor instance.","commonSituations":"Porting find()-cursor streaming code (which supports for await) to aggregation pipelines; using $lookup/$group on large result sets; switching streams to async iteration during a Node upgrade.","solutions":["Use eachAsync() on the cursor: await Model.aggregate(pipeline).cursor().eachAsync(doc => {...})","Drive the cursor manually with next(): let doc; while ((doc = await cursor.next()) !== null) {...}","If for await semantics are required, create a fresh aggregation cursor per iteration pass instead of reusing the executed one"],"exampleFix":"// before\nconst cursor = Model.aggregate(pipeline).cursor().exec();\nfor await (const doc of cursor) { handle(doc); } // throws\n\n// after\nawait Model.aggregate(pipeline).cursor().eachAsync(doc => handle(doc));","handlingStrategy":"fallback","validationCode":null,"typeGuard":"const isAggregationCursor = (c) =>\n  c != null && c.agg != null && typeof c.eachAsync === 'function';\n// aggregate cursors must go through eachAsync()/next(), never for await","tryCatchPattern":null,"preventionTips":["Default to eachAsync() for aggregation cursors — it is the supported streaming API","Reserve for await...of for Query cursors (Model.find().cursor()) which Mongoose can re-execute","In helpers that accept any cursor, branch on the cursor type before choosing the iteration style"],"tags":["aggregation","async-iterator","cursor","for-await","mongoose"],"backgroundTag":"async-iterator-unsupported","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}