mongodb/node-mongodb-native · error · MongoAPIError

ChangeStream cannot be used as an EventEmitter after being…

Error message

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

What it means

Thrown when a 'change' event listener is added to a ChangeStream that has already been used as an async iterator (via next(), hasNext(), tryNext(), or for-await-of). The ChangeStream enforces a single mode: either EventEmitter-style (event listeners) or iterator-style (async iteration). Switching from iterator to emitter is forbidden. This is a MongoAPIError.

Solutions

  1. Choose one consumption pattern (events or iterator) per ChangeStream instance and stick with it
  2. If you need to switch patterns, close the current change stream and create a new one with the same options/resume token
  3. Use the resume token from the closed stream to start the new one at the correct position

Example fix

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

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

Strategy: validation

Validate before calling

// Decide on one consumption mode before using the change stream
// Option A: events only
changeStream.on('change', handler);
// Option B: iterator only
for await (const change of changeStream) { ... }
// Do NOT mix the two on the same instance.

Try / catch

try {
  changeStream.on('change', handler);
} catch (error) {
  if (error instanceof MongoAPIError && error.message.includes('EventEmitter')) {
    // Stream was used as iterator; create a new stream for event mode
    const token = changeStream.resumeToken;
    const newStream = collection.watch(pipeline, { resumeAfter: token });
    newStream.on('change', handler);
  }
}

Prevention

When it happens

Trigger: First calling changeStream.next() or using for-await-of, then later calling changeStream.on('change', callback). The mode is set to 'iterator' by the first iterator-style call and cannot revert to 'emitter'.

Common situations: Refactoring code from for-await-of to event-based listening without creating a new change stream; mixing two consumer patterns in the same module; shared change stream instance used by both an iterator consumer and an event consumer.

Related errors


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

Appendix: source

Thrown at src/change_stream.ts:880

   *
   * NOTE: When using a Stream to process change stream events, the stream will
   * NOT automatically resume in the case a resumable error is encountered.
   *
   * @throws MongoChangeStreamError if the underlying cursor or the change stream is closed
   */
  stream(): Readable & AsyncIterable<TChange> {
    if (this.closed) {
      throw new MongoChangeStreamError(CHANGESTREAM_CLOSED_ERROR);
    }

    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';
  }

  /**

View on GitHub (pinned to dce7939f86)