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

Inverse of error 21: _setIsIterator() throws this MongoAPIError when you attempt to consume the ChangeStream with for-await after it has already been used as an EventEmitter (on('change', ...)). The driver tracks mode to prevent the cursor from being driven by two different mechanisms at once.

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 3366c21a63)

Solutions

  1. Stop using event listeners on this instance and consume it solely via for-await, or vice versa.
  2. Spin up a second ChangeStream via collection.watch() dedicated to the iterator consumption.
  3. Remove the .on('change') registration (and call close() if needed) before creating a new instance to iterate.

Example fix

// before
cs.on('change', handle);
for await (const change of cs) {} // throws

// after
cs.on('change', handle); // emitter mode only
Defensive patterns

Strategy: validation

Validate before calling

// Application-level: do not register listeners on instances you intend to iterate.
const MODE = Symbol('mode');
function iterate(cs) {
  if (cs.listenerCount('change') > 0) throw new Error('already in emitter mode');
}

Type guard

// No public mode field; model it in your own wrapper.
interface ManagedChangeStream { mode: 'emitter' | 'iterator' | null; }

Try / catch

try {
  for await (const c of cs) handle(c);
} catch (err) {
  if (err instanceof MongoAPIError && /iterator after being used as an EventEmitter/.test(err.message)) {
    // open a fresh change stream for iteration
  } else throw err;
}

Prevention

When it happens

Trigger: First registering `changeStream.on('change', cb)` (which starts emitter-mode pumping via _streamEvents), then later running `for await (const change of changeStream)` on the same instance.

Common situations: Layered code where a base module attaches listeners and a caller later tries to iterate; replacing a listener with iteration without removing the listener; integration tests that exercise both APIs on one instance.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04). Data as JSON: /data/errors/32ea2ad934675f1c.json. Report an issue: GitHub.