mongodb/node-mongodb-native · error · MongoAPIError
ChangeStream cannot be used as an EventEmitter after being u
Error message
ChangeStream cannot be used as an EventEmitter after being used as an iterator
What it means
A ChangeStream can be consumed in exactly one mode: EventEmitter (on('change', ...)) or async iterator (for-await). The internal _setIsEmitter() throws this MongoAPIError when you attach event listeners after the stream has already started being consumed with for-await-of. The mode is sticky and mutually exclusive to prevent two concurrent consumption loops racing on the same cursor.
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 3366c21a63)
Solutions
- Pick one consumption style per ChangeStream instance and remove the other.
- If you need both behaviors, create two separate ChangeStream instances via two collection.watch() calls.
- Refactor so iteration results are pushed into your own EventEmitter rather than reusing the driver's.
Example fix
// before
for await (const change of cs) { handle(change); }
cs.on('change', handle); // throws
// after
for await (const change of cs) { handle(change); } Defensive patterns
Strategy: validation
Validate before calling
// Enforce one consumption mode per instance at the call site
function assertEmitterMode(cs) {
if (cs.isClosed || /* iterator started */ false) throw new Error('already iterating');
} Type guard
type ChangeStreamMode = 'emitter' | 'iterator' | null; // Mode is internal; treat it as an application-level invariant: one style per instance.
Try / catch
try {
cs.on('change', handler);
} catch (err) {
if (err instanceof MongoAPIError && /EventEmitter after being used as an iterator/.test(err.message)) {
// create a new ChangeStream for emitter use
} else throw err;
} Prevention
- Standardize on one consumption pattern per change stream across your codebase.
- Document on each ChangeStream instance which mode it is intended for.
- Avoid mixing helper libraries that assume different modes on the same instance.
When it happens
Trigger: Running `for await (const change of changeStream)` and then, later, calling `changeStream.on('change', ...)` (or any event listener registration that triggers _streamEvents) on the same instance.
Common situations: Migrating code from event style to iterator style piecemeal; mixing a helper that iterates with another that listens; refactoring that leaves a stray .on('change') call after introducing a for-await loop.
Related errors
- ChangeStream cannot be used as an iterator after being used
- A change stream document has been received that lacks a resu
- ChangeStream is closed
- Clone not supported, create a new cursor with db.runCursorCo
- RunCommandCursor does not support readConcern it must be att
AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04).
Data as JSON: /data/errors/c34e53ff26c44563.json.
Report an issue: GitHub.