diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/events.js | 112 |
1 files changed, 106 insertions, 6 deletions
diff --git a/lib/events.js b/lib/events.js index a09333af44..ce93cd9f9b 100644 --- a/lib/events.js +++ b/lib/events.js @@ -23,6 +23,7 @@ const { Array, + Boolean, MathMin, NumberIsNaN, ObjectCreate, @@ -32,6 +33,7 @@ const { ReflectApply, ReflectOwnKeys, } = primordials; +const kRejection = Symbol.for('nodejs.rejection'); let spliceOne; @@ -49,8 +51,10 @@ const { inspect } = require('internal/util/inspect'); -function EventEmitter() { - EventEmitter.init.call(this); +const kCapture = Symbol('kCapture'); + +function EventEmitter(opts) { + EventEmitter.init.call(this, opts); } module.exports = EventEmitter; module.exports.once = once; @@ -60,6 +64,29 @@ EventEmitter.EventEmitter = EventEmitter; EventEmitter.usingDomains = false; +EventEmitter.captureRejectionSymbol = kRejection; +ObjectDefineProperty(EventEmitter, 'captureRejections', { + get() { + return EventEmitter.prototype[kCapture]; + }, + set(value) { + if (typeof value !== 'boolean') { + throw new ERR_INVALID_ARG_TYPE('EventEmitter.captureRejections', + 'boolean', value); + } + + EventEmitter.prototype[kCapture] = value; + }, + enumerable: true +}); + +// The default for captureRejections is false +ObjectDefineProperty(EventEmitter.prototype, kCapture, { + value: false, + writable: true, + enumerable: false +}); + EventEmitter.prototype._events = undefined; EventEmitter.prototype._eventsCount = 0; EventEmitter.prototype._maxListeners = undefined; @@ -89,7 +116,7 @@ ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', { } }); -EventEmitter.init = function() { +EventEmitter.init = function(opts) { if (this._events === undefined || this._events === ObjectGetPrototypeOf(this)._events) { @@ -98,8 +125,64 @@ EventEmitter.init = function() { } this._maxListeners = this._maxListeners || undefined; + + + if (opts && opts.captureRejections) { + if (typeof opts.captureRejections !== 'boolean') { + throw new ERR_INVALID_ARG_TYPE('options.captureRejections', + 'boolean', opts.captureRejections); + } + this[kCapture] = Boolean(opts.captureRejections); + } else { + // Assigning it directly a prototype lookup, as it slighly expensive + // and it sits in a very sensitive hot path. + this[kCapture] = EventEmitter.prototype[kCapture]; + } }; +function addCatch(that, promise, type, args) { + if (!that[kCapture]) { + return; + } + + // Handle Promises/A+ spec, then could be a getter + // that throws on second use. + try { + const then = promise.then; + + if (typeof then === 'function') { + then.call(promise, undefined, function(err) { + // The callback is called with nextTick to avoid a follow-up + // rejection from this promise. + process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args); + }); + } + } catch (err) { + that.emit('error', err); + } +} + +function emitUnhandledRejectionOrErr(ee, err, type, args) { + if (typeof ee[kRejection] === 'function') { + ee[kRejection](err, type, ...args); + } else { + // We have to disable the capture rejections mechanism, otherwise + // we might end up in an infinite loop. + const prev = ee[kCapture]; + + // If the error handler throws, it is not catcheable and it + // will end up in 'uncaughtException'. We restore the previous + // value of kCapture in case the uncaughtException is present + // and the exception is handled. + try { + ee[kCapture] = false; + ee.emit('error', err); + } finally { + ee[kCapture] = prev; + } + } +} + // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { @@ -216,12 +299,29 @@ EventEmitter.prototype.emit = function emit(type, ...args) { return false; if (typeof handler === 'function') { - ReflectApply(handler, this, args); + const result = ReflectApply(handler, this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } } else { const len = handler.length; const listeners = arrayClone(handler, len); - for (let i = 0; i < len; ++i) - ReflectApply(listeners[i], this, args); + for (var i = 0; i < len; ++i) { + const result = ReflectApply(listeners[i], this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty. + // This code is duplicated because extracting it away + // would make it non-inlineable. + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } } return true; |