Skip to content

Commit 94e1237

Browse files
authored
Merge pull request #14846 from Automattic/vkarpov15/changestream-sync-error
fix(cursor): throw error in ChangeStream constructor if `changeStreamThunk()` throws a sync error
2 parents 8f4070a + 9fa956f commit 94e1237

File tree

1 file changed

+43
-9
lines changed

1 file changed

+43
-9
lines changed

lib/cursor/changeStream.js

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
const EventEmitter = require('events').EventEmitter;
8+
const MongooseError = require('../error/mongooseError');
89

910
/*!
1011
* ignore
@@ -25,6 +26,7 @@ class ChangeStream extends EventEmitter {
2526
this.bindedEvents = false;
2627
this.pipeline = pipeline;
2728
this.options = options;
29+
this.errored = false;
2830

2931
if (options && options.hydrate && !options.model) {
3032
throw new Error(
@@ -33,19 +35,36 @@ class ChangeStream extends EventEmitter {
3335
);
3436
}
3537

38+
let syncError = null;
3639
this.$driverChangeStreamPromise = new Promise((resolve, reject) => {
3740
// This wrapper is necessary because of buffering.
38-
changeStreamThunk((err, driverChangeStream) => {
39-
if (err != null) {
40-
this.emit('error', err);
41-
return reject(err);
42-
}
41+
try {
42+
changeStreamThunk((err, driverChangeStream) => {
43+
if (err != null) {
44+
this.errored = true;
45+
this.emit('error', err);
46+
return reject(err);
47+
}
4348

44-
this.driverChangeStream = driverChangeStream;
45-
this.emit('ready');
46-
resolve();
47-
});
49+
this.driverChangeStream = driverChangeStream;
50+
this.emit('ready');
51+
resolve();
52+
});
53+
} catch (err) {
54+
syncError = err;
55+
this.errored = true;
56+
this.emit('error', err);
57+
reject(err);
58+
}
4859
});
60+
61+
// Because a ChangeStream is an event emitter, there's no way to register an 'error' handler
62+
// that catches errors which occur in the constructor, unless we force sync errors into async
63+
// errors with setImmediate(). For cleaner stack trace, we just immediately throw any synchronous
64+
// errors that occurred with changeStreamThunk().
65+
if (syncError != null) {
66+
throw syncError;
67+
}
4968
}
5069

5170
_bindEvents() {
@@ -92,10 +111,16 @@ class ChangeStream extends EventEmitter {
92111
}
93112

94113
hasNext(cb) {
114+
if (this.errored) {
115+
throw new MongooseError('Cannot call hasNext() on errored ChangeStream');
116+
}
95117
return this.driverChangeStream.hasNext(cb);
96118
}
97119

98120
next(cb) {
121+
if (this.errored) {
122+
throw new MongooseError('Cannot call next() on errored ChangeStream');
123+
}
99124
if (this.options && this.options.hydrate) {
100125
if (cb != null) {
101126
const originalCb = cb;
@@ -126,16 +151,25 @@ class ChangeStream extends EventEmitter {
126151
}
127152

128153
addListener(event, handler) {
154+
if (this.errored) {
155+
throw new MongooseError('Cannot call addListener() on errored ChangeStream');
156+
}
129157
this._bindEvents();
130158
return super.addListener(event, handler);
131159
}
132160

133161
on(event, handler) {
162+
if (this.errored) {
163+
throw new MongooseError('Cannot call on() on errored ChangeStream');
164+
}
134165
this._bindEvents();
135166
return super.on(event, handler);
136167
}
137168

138169
once(event, handler) {
170+
if (this.errored) {
171+
throw new MongooseError('Cannot call once() on errored ChangeStream');
172+
}
139173
this._bindEvents();
140174
return super.once(event, handler);
141175
}

0 commit comments

Comments
 (0)