Automattic/mongoose · error · MongooseError
Mongoose does not support using async iterators with an exis
Error message
Mongoose does not support using async iterators with an existing aggregation cursor. See https://bit.ly/mongoose-async-iterate-aggregation
What it means
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.
Source
Thrown at lib/cursor/aggregationCursor.js:166
if (!doc) {
_this.push(null);
_this.cursor.close(function(error) {
if (error) {
return _this.emit('error', error);
}
});
return;
}
_this.push(doc);
});
};
if (Symbol.asyncIterator != null) {
const msg = 'Mongoose does not support using async iterators with an ' +
'existing aggregation cursor. See https://bit.ly/mongoose-async-iterate-aggregation';
AggregationCursor.prototype[Symbol.asyncIterator] = function() {
throw new MongooseError(msg);
};
}
/**
* Registers a transform function which subsequently maps documents retrieved
* via the streams interface or `.next()`
*
* #### Example:
*
* // Map documents returned by `data` events
* Thing.
* find({ name: /^hello/ }).
* cursor().
* map(function (doc) {
* doc.foo = "bar";
* return doc;
* })
* on('data', function(doc) { console.log(doc.foo); });View on GitHub (pinned to 49cdab0136)
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
Example fix
// before
const cursor = Model.aggregate(pipeline).cursor().exec();
for await (const doc of cursor) { handle(doc); } // throws
// after
await Model.aggregate(pipeline).cursor().eachAsync(doc => handle(doc)); Defensive patterns
Strategy: fallback
Type guard
const isAggregationCursor = (c) => c != null && c.agg != null && typeof c.eachAsync === 'function'; // aggregate cursors must go through eachAsync()/next(), never for await
Prevention
- 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
When it happens
Trigger: const cursor = Model.aggregate(pipeline).cursor().exec(); for await (const doc of cursor) {...} — any for await...of loop over an existing AggregationCursor instance.
Common situations: 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.
Related errors
- batchSize must be a number
- batchSize must be an integer
- batchSize must be at least 1
- Invalid update pipeline operator: "${op}"
- Arguments must be aggregate pipeline operators
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/457b1242a1c40b54.
Report an issue: GitHub.