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

  1. Use eachAsync() on the cursor: await Model.aggregate(pipeline).cursor().eachAsync(doc => {...})
  2. Drive the cursor manually with next(): let doc; while ((doc = await cursor.next()) !== null) {...}
  3. 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

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


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