@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener'); |
86 | 86 |
|
87 | 87 | const kCapture = Symbol('kCapture'); |
88 | 88 | const kErrorMonitor = Symbol('events.errorMonitor'); |
89 | | -const kShapeMode = Symbol('shapeMode'); |
90 | 89 | const kEmitting = Symbol('events.emitting'); |
91 | 90 | const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners'); |
92 | 91 | const kMaxEventTargetListenersWarned = |
@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) { |
335 | 334 | this._events === ObjectGetPrototypeOf(this)._events) { |
336 | 335 | this._events = { __proto__: null }; |
337 | 336 | this._eventsCount = 0; |
338 | | - this[kShapeMode] = false; |
339 | | - } else { |
340 | | - this[kShapeMode] = true; |
341 | 337 | } |
342 | 338 |
|
343 | 339 | this._maxListeners ||= undefined; |
@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) { |
446 | 442 | return err.stack + sep + ArrayPrototypeJoin(ownStack, '\n'); |
447 | 443 | } |
448 | 444 |
|
| 445 | +function getUnhandledErrorException(ee, args) { |
| 446 | + let er; |
| 447 | + if (args.length > 0) |
| 448 | + er = args[0]; |
| 449 | + if (er instanceof Error) { |
| 450 | + try { |
| 451 | + const capture = {}; |
| 452 | + ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit); |
| 453 | + ObjectDefineProperty(er, kEnhanceStackBeforeInspector, { |
| 454 | + __proto__: null, |
| 455 | + value: FunctionPrototypeBind(enhanceStackTrace, ee, er, capture), |
| 456 | + configurable: true, |
| 457 | + }); |
| 458 | + } catch { |
| 459 | + // Continue regardless of error. |
| 460 | + } |
| 461 | + |
| 462 | + return er; |
| 463 | + } |
| 464 | + |
| 465 | + let stringifiedEr; |
| 466 | + try { |
| 467 | + stringifiedEr = inspect(er); |
| 468 | + } catch { |
| 469 | + stringifiedEr = er; |
| 470 | + } |
| 471 | + |
| 472 | + // At least give some kind of context to the user |
| 473 | + const err = new ERR_UNHANDLED_ERROR(stringifiedEr); |
| 474 | + err.context = er; |
| 475 | + return err; |
| 476 | +} |
| 477 | + |
449 | 478 | /** |
450 | 479 | * Synchronously calls each of the listeners registered |
451 | 480 | * for the event. |
@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) { |
466 | 495 |
|
467 | 496 | // If there is no 'error' event listener then throw. |
468 | 497 | if (doError) { |
469 | | - let er; |
470 | | - if (args.length > 0) |
471 | | - er = args[0]; |
472 | | - if (er instanceof Error) { |
473 | | - try { |
474 | | - const capture = {}; |
475 | | - ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit); |
476 | | - ObjectDefineProperty(er, kEnhanceStackBeforeInspector, { |
477 | | - __proto__: null, |
478 | | - value: FunctionPrototypeBind(enhanceStackTrace, this, er, capture), |
479 | | - configurable: true, |
480 | | - }); |
481 | | - } catch { |
482 | | - // Continue regardless of error. |
483 | | - } |
484 | | - |
485 | | - // Note: The comments on the `throw` lines are intentional, they show |
486 | | - // up in Node's output if this results in an unhandled exception. |
487 | | - throw er; // Unhandled 'error' event |
488 | | - } |
489 | | - |
490 | | - let stringifiedEr; |
491 | | - try { |
492 | | - stringifiedEr = inspect(er); |
493 | | - } catch { |
494 | | - stringifiedEr = er; |
495 | | - } |
496 | | - |
497 | | - // At least give some kind of context to the user |
498 | | - const err = new ERR_UNHANDLED_ERROR(stringifiedEr); |
499 | | - err.context = er; |
500 | | - throw err; // Unhandled 'error' event |
| 498 | + const er = getUnhandledErrorException(this, args); |
| 499 | + // Note: The comments on the `throw` lines are intentional, they show |
| 500 | + // up in Node's output if this results in an unhandled exception. |
| 501 | + throw er; // Unhandled 'error' event |
501 | 502 | } |
502 | 503 |
|
503 | 504 | const handler = events[type]; |
@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) { |
584 | 585 |
|
585 | 586 | // Check for listener leak |
586 | 587 | m = _getMaxListeners(target); |
587 | | - if (m > 0 && existing.length > m && !existing.warned) { |
588 | | - existing.warned = true; |
589 | | - // No error code for this since it is a Warning |
590 | | - const w = genericNodeError( |
591 | | - `Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` + |
592 | | - `added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`, |
593 | | - { name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length }); |
594 | | - process.emitWarning(w); |
595 | | - } |
| 588 | + if (m > 0 && existing.length > m && !existing.warned) |
| 589 | + warnMaxListenersExceeded(target, type, existing, m); |
596 | 590 | } |
597 | 591 |
|
598 | 592 | return target; |
599 | 593 | } |
600 | 594 |
|
| 595 | +function warnMaxListenersExceeded(target, type, existing, m) { |
| 596 | + existing.warned = true; |
| 597 | + // No error code for this since it is a Warning |
| 598 | + const w = genericNodeError( |
| 599 | + `Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` + |
| 600 | + `added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`, |
| 601 | + { name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length }); |
| 602 | + process.emitWarning(w); |
| 603 | +} |
| 604 | + |
601 | 605 | /** |
602 | 606 | * Adds a listener to the event emitter. |
603 | 607 | * @param {string | symbol} type |
@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener = |
622 | 626 | return _addListener(this, type, listener, true); |
623 | 627 | }; |
624 | 628 |
|
625 | | -function onceWrapper() { |
626 | | - if (!this.fired) { |
627 | | - this.target.removeListener(this.type, this.wrapFn); |
628 | | - this.fired = true; |
629 | | - if (arguments.length === 0) |
630 | | - return this.listener.call(this.target); |
631 | | - return this.listener.apply(this.target, arguments); |
632 | | - } |
633 | | -} |
634 | | - |
635 | 629 | function _onceWrap(target, type, listener) { |
636 | | - const state = { fired: false, wrapFn: undefined, target, type, listener }; |
637 | | - const wrapped = onceWrapper.bind(state); |
638 | | - wrapped.listener = listener; |
639 | | - state.wrapFn = wrapped; |
640 | | - return wrapped; |
| 630 | + let fired = false; |
| 631 | + function wrapper(...args) { |
| 632 | + if (fired) return; |
| 633 | + fired = true; |
| 634 | + target.removeListener(type, wrapper); |
| 635 | + return ReflectApply(listener, target, args); |
| 636 | + } |
| 637 | + wrapper.listener = listener; |
| 638 | + return wrapper; |
641 | 639 | } |
642 | 640 |
|
643 | 641 | /** |
@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener = |
689 | 687 | if (list === listener || list.listener === listener) { |
690 | 688 | this._eventsCount -= 1; |
691 | 689 |
|
692 | | - if (this[kShapeMode]) { |
693 | | - events[type] = undefined; |
694 | | - } else if (this._eventsCount === 0) { |
695 | | - this._events = { __proto__: null }; |
696 | | - } else { |
697 | | - delete events[type]; |
698 | | - } |
| 690 | + // Leave the key in place with an `undefined` value: repeatedly |
| 691 | + // adding and removing a listener for the same event this way keeps |
| 692 | + // the `events` object in the same shape and avoids both a `delete` |
| 693 | + // (which would put the object into dictionary mode) and allocating |
| 694 | + // a fresh object when the last listener is removed. |
| 695 | + events[type] = undefined; |
699 | 696 |
|
700 | 697 | if (events.removeListener !== undefined) |
701 | 698 | this.emit('removeListener', type, list.listener || listener); |
@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners = |
755 | 752 | else |
756 | 753 | delete events[type]; |
757 | 754 | } |
758 | | - this[kShapeMode] = false; |
759 | 755 | return this; |
760 | 756 | } |
761 | 757 |
|
@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners = |
768 | 764 | this.removeAllListeners('removeListener'); |
769 | 765 | this._events = { __proto__: null }; |
770 | 766 | this._eventsCount = 0; |
771 | | - this[kShapeMode] = false; |
772 | 767 | return this; |
773 | 768 | } |
774 | 769 |
|
@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) { |
868 | 863 | * @returns {(string | symbol)[]} |
869 | 864 | */ |
870 | 865 | EventEmitter.prototype.eventNames = function eventNames() { |
871 | | - return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; |
| 866 | + if (this._eventsCount === 0) |
| 867 | + return []; |
| 868 | + const events = this._events; |
| 869 | + const names = []; |
| 870 | + for (const key of ReflectOwnKeys(events)) { |
| 871 | + // Removed listeners leave the key in place with an `undefined` value. |
| 872 | + if (events[key] !== undefined) |
| 873 | + ArrayPrototypePush(names, key); |
| 874 | + } |
| 875 | + return names; |
872 | 876 | }; |
873 | 877 |
|
874 | 878 | function arrayClone(arr) { |
|