mongodb/node-mongodb-native · error · MongoAPIError

ChangeStream cannot be used as an iterator after being used…

Error message

ChangeStream cannot be used as an iterator after being used as an EventEmitter

What it means

Thrown when an iterator-style method (next(), hasNext(), tryNext(), or for-await-of) is called on a ChangeStream that has already been used as an EventEmitter (a 'change' listener was attached). The ChangeStream enforces a single consumption mode and switching from emitter to iterator is forbidden. This is a MongoAPIError.

Solutions

  1. Choose one consumption pattern per ChangeStream instance from the start
  2. If you must switch, create a new change stream using the resume token from the previous one
  3. Remove all 'change' listeners and close the stream before creating a new iterator-based stream

Example fix

// before
changeStream.on('change', handler); // sets emitter mode
const doc = await changeStream.next(); // throws

// after
const token = changeStream.resumeToken;
await changeStream.close();
const newStream = collection.watch(pipeline, { resumeAfter: token });
for await (const change of newStream) {
  handler(change);
}
Defensive patterns

Strategy: validation

Validate before calling

// Decide on one consumption mode before using the change stream
// If events are attached, do not call iterator methods
if (changeStream.listenerCount('change') > 0) {
  throw new Error('Cannot use iterator methods after attaching change listeners');
}
const doc = await changeStream.next();

Try / catch

try {
  const doc = await changeStream.next();
} catch (error) {
  if (error instanceof MongoAPIError && error.message.includes('iterator')) {
    // Stream is in emitter mode; create new stream for iteration
    const token = changeStream.resumeToken;
    const newStream = collection.watch(pipeline, { resumeAfter: token });
    doc = await newStream.next();
  }
}

Prevention

When it happens

Trigger: First calling changeStream.on('change', callback) (which sets emitter mode via _setIsEmitter), then later calling changeStream.next() or using for-await-of on the same instance.

Common situations: Refactoring from event listeners to async iteration without recreating the stream; shared change stream instances across modules where one attaches events and another iterates; code that attaches a temporary listener then tries to iterate.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11). Data as JSON: /api/errors/32ea2ad934675f1c. Report an issue: GitHub.

Appendix: source

Thrown at src/change_stream.ts:891

    return this.cursor.stream();
  }

  /** @internal */
  private _setIsEmitter(): void {
    if (this.mode === 'iterator') {
      // TODO(NODE-3485): Replace with MongoChangeStreamModeError
      throw new MongoAPIError(
        'ChangeStream cannot be used as an EventEmitter after being used as an iterator'
      );
    }
    this.mode = 'emitter';
  }

  /** @internal */
  private _setIsIterator(): void {
    if (this.mode === 'emitter') {
      // TODO(NODE-3485): Replace with MongoChangeStreamModeError
      throw new MongoAPIError(
        'ChangeStream cannot be used as an iterator after being used as an EventEmitter'
      );
    }
    this.mode = 'iterator';
  }

  /**
   * Create a new change stream cursor based on self's configuration
   * @internal
   */
  private _createChangeStreamCursor(
    options: ChangeStreamOptions | ChangeStreamCursorOptions
  ): ChangeStreamCursor<TSchema, TChange> {
    const changeStreamStageOptions: Document = filterOutOptions(options);
    if (this.type === CHANGE_DOMAIN_TYPES.CLUSTER) {
      changeStreamStageOptions.allChangesForCluster = true;
    }
    const pipeline = [{ $changeStream: changeStreamStageOptions }, ...this.pipeline];

View on GitHub (pinned to dce7939f86)