Skip to content

Commit 7ac188c

Browse files
committed
add captureRejection option
Port of nodejs/node#27867
1 parent 48e3d18 commit 7ac188c

File tree

3 files changed

+426
-6
lines changed

3 files changed

+426
-6
lines changed

events.js

Lines changed: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,38 @@ var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {
5050
return value !== value;
5151
}
5252

53-
function EventEmitter() {
54-
EventEmitter.init.call(this);
53+
var kCapture = typeof Symbol === 'function' ? Symbol('kCapture') : '_fakeSymbol_kCapture';
54+
var kRejection = typeof Symbol === 'function' && typeof Symbol.for === 'function' ? Symbol.for('nodejs.rejection') : '_fakeSymbol_nodejs.rejection';
55+
56+
function EventEmitter(opts) {
57+
EventEmitter.init.call(this, opts);
5558
}
5659
module.exports = EventEmitter;
5760
module.exports.once = once;
5861

5962
// Backwards-compat with node 0.10.x
6063
EventEmitter.EventEmitter = EventEmitter;
6164

65+
EventEmitter.captureRejectionSymbol = kRejection;
66+
Object.defineProperty(EventEmitter, 'captureRejections', {
67+
get: function () {
68+
return EventEmitter.prototype[kCapture];
69+
},
70+
set: function (value) {
71+
if (typeof value !== 'boolean') {
72+
throw new TypeError('The "EventEmitter.captureRejections" argument must be of type boolean. Received type ' + typeof value);
73+
}
74+
EventEmitter.prototype[kCapture] = value;
75+
},
76+
enumerable: true
77+
});
78+
79+
Object.defineProperty(EventEmitter.prototype, kCapture, {
80+
value: false,
81+
writable: true,
82+
enumerable: false
83+
});
84+
6285
EventEmitter.prototype._events = undefined;
6386
EventEmitter.prototype._eventsCount = 0;
6487
EventEmitter.prototype._maxListeners = undefined;
@@ -86,7 +109,7 @@ Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
86109
}
87110
});
88111

89-
EventEmitter.init = function() {
112+
EventEmitter.init = function init(opts) {
90113

91114
if (this._events === undefined ||
92115
this._events === Object.getPrototypeOf(this)._events) {
@@ -95,8 +118,61 @@ EventEmitter.init = function() {
95118
}
96119

97120
this._maxListeners = this._maxListeners || undefined;
121+
122+
if (opts && opts.captureRejections) {
123+
if (typeof opts.captureRejections !== 'boolean') {
124+
throw new TypeError('The "options.captureRejections" argument must be of type boolean. Received type ' + typeof opts.captureRejections);
125+
}
126+
this[kCapture] = Boolean(opts.captureRejections);
127+
} else {
128+
this[kCapture] = EventEmitter.prototype[kCapture];
129+
}
98130
};
99131

132+
var ProcessNextTick = typeof queueMicrotask === 'function'
133+
? queueMicrotask
134+
: typeof setImmediate === 'function'
135+
? setImmediate
136+
: setTimeout;
137+
138+
function addCatch(that, promise, type, args) {
139+
if (!that[kCapture]) {
140+
return;
141+
}
142+
143+
// Handle Promises/A+ spec, then could be a getter
144+
// that throws on second use.
145+
try {
146+
var then = promise.then;
147+
if (typeof then === 'function') {
148+
then.call(promise, undefined, function(err) {
149+
ProcessNextTick(function () {
150+
emitUnhandledRejectionOrErr(that, err, type, args);
151+
});
152+
});
153+
}
154+
} catch (err) {
155+
that.emit('error', err);
156+
}
157+
}
158+
159+
function emitUnhandledRejectionOrErr(ee, err, type, args) {
160+
if (typeof ee[kRejection] === 'function') {
161+
ee[kRejection].apply(ee, [err, type].concat(args));
162+
} else {
163+
// We have to disable the capture rejections mechanism, otherwise
164+
// we might end up in an infinite loop.
165+
var prev = ee[kCapture];
166+
167+
try {
168+
ee[kCapture] = false;
169+
ee.emit('error', err);
170+
} finally {
171+
ee[kCapture] = prev;
172+
}
173+
}
174+
}
175+
100176
// Obviously not all Emitters should be limited to 10. This function allows
101177
// that to be increased. Set to zero for unlimited.
102178
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
@@ -150,12 +226,29 @@ EventEmitter.prototype.emit = function emit(type) {
150226
return false;
151227

152228
if (typeof handler === 'function') {
153-
ReflectApply(handler, this, args);
229+
var result = ReflectApply(handler, this, args);
230+
231+
// We check if result is undefined first because that
232+
// is the most common case so we do not pay any perf
233+
// penalty
234+
if (result !== undefined && result !== null) {
235+
addCatch(this, result, type, args);
236+
}
154237
} else {
155238
var len = handler.length;
156239
var listeners = arrayClone(handler, len);
157-
for (var i = 0; i < len; ++i)
158-
ReflectApply(listeners[i], this, args);
240+
for (var i = 0; i < len; ++i) {
241+
var result = ReflectApply(listeners[i], this, args);
242+
243+
// We check if result is undefined first because that
244+
// is the most common case so we do not pay any perf
245+
// penalty.
246+
// This code is duplicated because extracting it away
247+
// would make it non-inlineable.
248+
if (result !== undefined && result !== null) {
249+
addCatch(this, result, type, args);
250+
}
251+
}
159252
}
160253

161254
return true;

0 commit comments

Comments
 (0)