Commit 73e1701

Browse files
mcollinaaduh95
authored andcommitted
events: optimize once() and removeListener()
Use a closure instead of a bound state object for the once() wrapper, keep the events object shape in removeListener() by storing undefined instead of deleting keys or reallocating the object, and outline the cold unhandled 'error' path from emit() and the max-listeners warning from _addListener() so both fit within V8 inlining budgets. ee-once +25%, ee-add-remove +12-22%, ee-listen-unique +7-15%, streams/pipe +13.9%. Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64373 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 86bca58 commit 73e1701

5 files changed

Lines changed: 82 additions & 78 deletions

‎lib/events.js‎

Lines changed: 74 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');
8686

8787
constkCapture=Symbol('kCapture');
8888
constkErrorMonitor=Symbol('events.errorMonitor');
89-
constkShapeMode=Symbol('shapeMode');
9089
constkEmitting=Symbol('events.emitting');
9190
constkMaxEventTargetListeners=Symbol('events.maxEventTargetListeners');
9291
constkMaxEventTargetListenersWarned=
@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
335334
this._events===ObjectGetPrototypeOf(this)._events){
336335
this._events={__proto__: null};
337336
this._eventsCount=0;
338-
this[kShapeMode]=false;
339-
}else{
340-
this[kShapeMode]=true;
341337
}
342338

343339
this._maxListeners||=undefined;
@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
446442
returnerr.stack+sep+ArrayPrototypeJoin(ownStack,'\n');
447443
}
448444

445+
functiongetUnhandledErrorException(ee,args){
446+
leter;
447+
if(args.length>0)
448+
er=args[0];
449+
if(erinstanceofError){
450+
try{
451+
constcapture={};
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+
returner;
463+
}
464+
465+
letstringifiedEr;
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+
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
474+
err.context=er;
475+
returnerr;
476+
}
477+
449478
/**
450479
* Synchronously calls each of the listeners registered
451480
* for the event.
@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
466495

467496
// If there is no 'error' event listener then throw.
468497
if(doError){
469-
leter;
470-
if(args.length>0)
471-
er=args[0];
472-
if(erinstanceofError){
473-
try{
474-
constcapture={};
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-
thrower;// Unhandled 'error' event
488-
}
489-
490-
letstringifiedEr;
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-
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
499-
err.context=er;
500-
throwerr;// Unhandled 'error' event
498+
conster=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+
thrower;// Unhandled 'error' event
501502
}
502503

503504
consthandler=events[type];
@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {
584585

585586
// Check for listener leak
586587
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-
constw=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);
596590
}
597591

598592
returntarget;
599593
}
600594

595+
functionwarnMaxListenersExceeded(target,type,existing,m){
596+
existing.warned=true;
597+
// No error code for this since it is a Warning
598+
constw=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+
601605
/**
602606
* Adds a listener to the event emitter.
603607
* @param {string | symbol} type
@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
622626
return_addListener(this,type,listener,true);
623627
};
624628

625-
functiononceWrapper(){
626-
if(!this.fired){
627-
this.target.removeListener(this.type,this.wrapFn);
628-
this.fired=true;
629-
if(arguments.length===0)
630-
returnthis.listener.call(this.target);
631-
returnthis.listener.apply(this.target,arguments);
632-
}
633-
}
634-
635629
function_onceWrap(target,type,listener){
636-
conststate={fired: false,wrapFn: undefined, target, type, listener };
637-
constwrapped=onceWrapper.bind(state);
638-
wrapped.listener=listener;
639-
state.wrapFn=wrapped;
640-
returnwrapped;
630+
letfired=false;
631+
functionwrapper(...args){
632+
if(fired)return;
633+
fired=true;
634+
target.removeListener(type,wrapper);
635+
returnReflectApply(listener,target,args);
636+
}
637+
wrapper.listener=listener;
638+
returnwrapper;
641639
}
642640

643641
/**
@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
689687
if(list===listener||list.listener===listener){
690688
this._eventsCount-=1;
691689

692-
if(this[kShapeMode]){
693-
events[type]=undefined;
694-
}elseif(this._eventsCount===0){
695-
this._events={__proto__: null};
696-
}else{
697-
deleteevents[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;
699696

700697
if(events.removeListener!==undefined)
701698
this.emit('removeListener',type,list.listener||listener);
@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
755752
else
756753
deleteevents[type];
757754
}
758-
this[kShapeMode]=false;
759755
returnthis;
760756
}
761757

@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
768764
this.removeAllListeners('removeListener');
769765
this._events={__proto__: null};
770766
this._eventsCount=0;
771-
this[kShapeMode]=false;
772767
returnthis;
773768
}
774769

@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
868863
* @returns {(string | symbol)[]}
869864
*/
870865
EventEmitter.prototype.eventNames=functioneventNames(){
871-
returnthis._eventsCount>0 ? ReflectOwnKeys(this._events) : [];
866+
if(this._eventsCount===0)
867+
return[];
868+
constevents=this._events;
869+
constnames=[];
870+
for(constkeyofReflectOwnKeys(events)){
871+
// Removed listeners leave the key in place with an `undefined` value.
872+
if(events[key]!==undefined)
873+
ArrayPrototypePush(names,key);
874+
}
875+
returnnames;
872876
};
873877

874878
functionarrayClone(arr){

‎test/fixtures/errors/events_unhandled_error_common_trace.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error: foo:bar
66
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)

‎test/fixtures/errors/events_unhandled_error_nexttick.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)

‎test/fixtures/errors/events_unhandled_error_sameline.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)

‎test/fixtures/errors/events_unhandled_error_subclass.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit 73e1701

Browse files
mcollinaaduh95
authored andcommitted
events: optimize once() and removeListener()
Use a closure instead of a bound state object for the once() wrapper, keep the events object shape in removeListener() by storing undefined instead of deleting keys or reallocating the object, and outline the cold unhandled 'error' path from emit() and the max-listeners warning from _addListener() so both fit within V8 inlining budgets. ee-once +25%, ee-add-remove +12-22%, ee-listen-unique +7-15%, streams/pipe +13.9%. Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64373 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 86bca58 commit 73e1701

5 files changed

Lines changed: 82 additions & 78 deletions

‎lib/events.js‎

Lines changed: 74 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');
8686

8787
constkCapture=Symbol('kCapture');
8888
constkErrorMonitor=Symbol('events.errorMonitor');
89-
constkShapeMode=Symbol('shapeMode');
9089
constkEmitting=Symbol('events.emitting');
9190
constkMaxEventTargetListeners=Symbol('events.maxEventTargetListeners');
9291
constkMaxEventTargetListenersWarned=
@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
335334
this._events===ObjectGetPrototypeOf(this)._events){
336335
this._events={__proto__: null};
337336
this._eventsCount=0;
338-
this[kShapeMode]=false;
339-
}else{
340-
this[kShapeMode]=true;
341337
}
342338

343339
this._maxListeners||=undefined;
@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
446442
returnerr.stack+sep+ArrayPrototypeJoin(ownStack,'\n');
447443
}
448444

445+
functiongetUnhandledErrorException(ee,args){
446+
leter;
447+
if(args.length>0)
448+
er=args[0];
449+
if(erinstanceofError){
450+
try{
451+
constcapture={};
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+
returner;
463+
}
464+
465+
letstringifiedEr;
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+
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
474+
err.context=er;
475+
returnerr;
476+
}
477+
449478
/**
450479
* Synchronously calls each of the listeners registered
451480
* for the event.
@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
466495

467496
// If there is no 'error' event listener then throw.
468497
if(doError){
469-
leter;
470-
if(args.length>0)
471-
er=args[0];
472-
if(erinstanceofError){
473-
try{
474-
constcapture={};
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-
thrower;// Unhandled 'error' event
488-
}
489-
490-
letstringifiedEr;
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-
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
499-
err.context=er;
500-
throwerr;// Unhandled 'error' event
498+
conster=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+
thrower;// Unhandled 'error' event
501502
}
502503

503504
consthandler=events[type];
@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {
584585

585586
// Check for listener leak
586587
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-
constw=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);
596590
}
597591

598592
returntarget;
599593
}
600594

595+
functionwarnMaxListenersExceeded(target,type,existing,m){
596+
existing.warned=true;
597+
// No error code for this since it is a Warning
598+
constw=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+
601605
/**
602606
* Adds a listener to the event emitter.
603607
* @param {string | symbol} type
@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
622626
return_addListener(this,type,listener,true);
623627
};
624628

625-
functiononceWrapper(){
626-
if(!this.fired){
627-
this.target.removeListener(this.type,this.wrapFn);
628-
this.fired=true;
629-
if(arguments.length===0)
630-
returnthis.listener.call(this.target);
631-
returnthis.listener.apply(this.target,arguments);
632-
}
633-
}
634-
635629
function_onceWrap(target,type,listener){
636-
conststate={fired: false,wrapFn: undefined, target, type, listener };
637-
constwrapped=onceWrapper.bind(state);
638-
wrapped.listener=listener;
639-
state.wrapFn=wrapped;
640-
returnwrapped;
630+
letfired=false;
631+
functionwrapper(...args){
632+
if(fired)return;
633+
fired=true;
634+
target.removeListener(type,wrapper);
635+
returnReflectApply(listener,target,args);
636+
}
637+
wrapper.listener=listener;
638+
returnwrapper;
641639
}
642640

643641
/**
@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
689687
if(list===listener||list.listener===listener){
690688
this._eventsCount-=1;
691689

692-
if(this[kShapeMode]){
693-
events[type]=undefined;
694-
}elseif(this._eventsCount===0){
695-
this._events={__proto__: null};
696-
}else{
697-
deleteevents[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;
699696

700697
if(events.removeListener!==undefined)
701698
this.emit('removeListener',type,list.listener||listener);
@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
755752
else
756753
deleteevents[type];
757754
}
758-
this[kShapeMode]=false;
759755
returnthis;
760756
}
761757

@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
768764
this.removeAllListeners('removeListener');
769765
this._events={__proto__: null};
770766
this._eventsCount=0;
771-
this[kShapeMode]=false;
772767
returnthis;
773768
}
774769

@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
868863
* @returns {(string | symbol)[]}
869864
*/
870865
EventEmitter.prototype.eventNames=functioneventNames(){
871-
returnthis._eventsCount>0 ? ReflectOwnKeys(this._events) : [];
866+
if(this._eventsCount===0)
867+
return[];
868+
constevents=this._events;
869+
constnames=[];
870+
for(constkeyofReflectOwnKeys(events)){
871+
// Removed listeners leave the key in place with an `undefined` value.
872+
if(events[key]!==undefined)
873+
ArrayPrototypePush(names,key);
874+
}
875+
returnnames;
872876
};
873877

874878
functionarrayClone(arr){

‎test/fixtures/errors/events_unhandled_error_common_trace.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error: foo:bar
66
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)

‎test/fixtures/errors/events_unhandled_error_nexttick.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)

‎test/fixtures/errors/events_unhandled_error_sameline.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)

‎test/fixtures/errors/events_unhandled_error_subclass.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 73e1701

Browse files
mcollinaaduh95
authored andcommitted
events: optimize once() and removeListener()
Use a closure instead of a bound state object for the once() wrapper, keep the events object shape in removeListener() by storing undefined instead of deleting keys or reallocating the object, and outline the cold unhandled 'error' path from emit() and the max-listeners warning from _addListener() so both fit within V8 inlining budgets. ee-once +25%, ee-add-remove +12-22%, ee-listen-unique +7-15%, streams/pipe +13.9%. Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64373 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 86bca58 commit 73e1701

5 files changed

Lines changed: 82 additions & 78 deletions

‎lib/events.js‎

Lines changed: 74 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');
8686

8787
constkCapture=Symbol('kCapture');
8888
constkErrorMonitor=Symbol('events.errorMonitor');
89-
constkShapeMode=Symbol('shapeMode');
9089
constkEmitting=Symbol('events.emitting');
9190
constkMaxEventTargetListeners=Symbol('events.maxEventTargetListeners');
9291
constkMaxEventTargetListenersWarned=
@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
335334
this._events===ObjectGetPrototypeOf(this)._events){
336335
this._events={__proto__: null};
337336
this._eventsCount=0;
338-
this[kShapeMode]=false;
339-
}else{
340-
this[kShapeMode]=true;
341337
}
342338

343339
this._maxListeners||=undefined;
@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
446442
returnerr.stack+sep+ArrayPrototypeJoin(ownStack,'\n');
447443
}
448444

445+
functiongetUnhandledErrorException(ee,args){
446+
leter;
447+
if(args.length>0)
448+
er=args[0];
449+
if(erinstanceofError){
450+
try{
451+
constcapture={};
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+
returner;
463+
}
464+
465+
letstringifiedEr;
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+
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
474+
err.context=er;
475+
returnerr;
476+
}
477+
449478
/**
450479
* Synchronously calls each of the listeners registered
451480
* for the event.
@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
466495

467496
// If there is no 'error' event listener then throw.
468497
if(doError){
469-
leter;
470-
if(args.length>0)
471-
er=args[0];
472-
if(erinstanceofError){
473-
try{
474-
constcapture={};
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-
thrower;// Unhandled 'error' event
488-
}
489-
490-
letstringifiedEr;
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-
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
499-
err.context=er;
500-
throwerr;// Unhandled 'error' event
498+
conster=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+
thrower;// Unhandled 'error' event
501502
}
502503

503504
consthandler=events[type];
@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {
584585

585586
// Check for listener leak
586587
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-
constw=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);
596590
}
597591

598592
returntarget;
599593
}
600594

595+
functionwarnMaxListenersExceeded(target,type,existing,m){
596+
existing.warned=true;
597+
// No error code for this since it is a Warning
598+
constw=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+
601605
/**
602606
* Adds a listener to the event emitter.
603607
* @param {string | symbol} type
@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
622626
return_addListener(this,type,listener,true);
623627
};
624628

625-
functiononceWrapper(){
626-
if(!this.fired){
627-
this.target.removeListener(this.type,this.wrapFn);
628-
this.fired=true;
629-
if(arguments.length===0)
630-
returnthis.listener.call(this.target);
631-
returnthis.listener.apply(this.target,arguments);
632-
}
633-
}
634-
635629
function_onceWrap(target,type,listener){
636-
conststate={fired: false,wrapFn: undefined, target, type, listener };
637-
constwrapped=onceWrapper.bind(state);
638-
wrapped.listener=listener;
639-
state.wrapFn=wrapped;
640-
returnwrapped;
630+
letfired=false;
631+
functionwrapper(...args){
632+
if(fired)return;
633+
fired=true;
634+
target.removeListener(type,wrapper);
635+
returnReflectApply(listener,target,args);
636+
}
637+
wrapper.listener=listener;
638+
returnwrapper;
641639
}
642640

643641
/**
@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
689687
if(list===listener||list.listener===listener){
690688
this._eventsCount-=1;
691689

692-
if(this[kShapeMode]){
693-
events[type]=undefined;
694-
}elseif(this._eventsCount===0){
695-
this._events={__proto__: null};
696-
}else{
697-
deleteevents[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;
699696

700697
if(events.removeListener!==undefined)
701698
this.emit('removeListener',type,list.listener||listener);
@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
755752
else
756753
deleteevents[type];
757754
}
758-
this[kShapeMode]=false;
759755
returnthis;
760756
}
761757

@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
768764
this.removeAllListeners('removeListener');
769765
this._events={__proto__: null};
770766
this._eventsCount=0;
771-
this[kShapeMode]=false;
772767
returnthis;
773768
}
774769

@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
868863
* @returns {(string | symbol)[]}
869864
*/
870865
EventEmitter.prototype.eventNames=functioneventNames(){
871-
returnthis._eventsCount>0 ? ReflectOwnKeys(this._events) : [];
866+
if(this._eventsCount===0)
867+
return[];
868+
constevents=this._events;
869+
constnames=[];
870+
for(constkeyofReflectOwnKeys(events)){
871+
// Removed listeners leave the key in place with an `undefined` value.
872+
if(events[key]!==undefined)
873+
ArrayPrototypePush(names,key);
874+
}
875+
returnnames;
872876
};
873877

874878
functionarrayClone(arr){

‎test/fixtures/errors/events_unhandled_error_common_trace.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error: foo:bar
66
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)

‎test/fixtures/errors/events_unhandled_error_nexttick.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)

‎test/fixtures/errors/events_unhandled_error_sameline.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)

‎test/fixtures/errors/events_unhandled_error_subclass.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 73e1701

Browse files
mcollinaaduh95
authored andcommitted
events: optimize once() and removeListener()
Use a closure instead of a bound state object for the once() wrapper, keep the events object shape in removeListener() by storing undefined instead of deleting keys or reallocating the object, and outline the cold unhandled 'error' path from emit() and the max-listeners warning from _addListener() so both fit within V8 inlining budgets. ee-once +25%, ee-add-remove +12-22%, ee-listen-unique +7-15%, streams/pipe +13.9%. Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64373 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 86bca58 commit 73e1701

5 files changed

Lines changed: 82 additions & 78 deletions

‎lib/events.js‎

Lines changed: 74 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');
8686

8787
constkCapture=Symbol('kCapture');
8888
constkErrorMonitor=Symbol('events.errorMonitor');
89-
constkShapeMode=Symbol('shapeMode');
9089
constkEmitting=Symbol('events.emitting');
9190
constkMaxEventTargetListeners=Symbol('events.maxEventTargetListeners');
9291
constkMaxEventTargetListenersWarned=
@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
335334
this._events===ObjectGetPrototypeOf(this)._events){
336335
this._events={__proto__: null};
337336
this._eventsCount=0;
338-
this[kShapeMode]=false;
339-
}else{
340-
this[kShapeMode]=true;
341337
}
342338

343339
this._maxListeners||=undefined;
@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
446442
returnerr.stack+sep+ArrayPrototypeJoin(ownStack,'\n');
447443
}
448444

445+
functiongetUnhandledErrorException(ee,args){
446+
leter;
447+
if(args.length>0)
448+
er=args[0];
449+
if(erinstanceofError){
450+
try{
451+
constcapture={};
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+
returner;
463+
}
464+
465+
letstringifiedEr;
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+
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
474+
err.context=er;
475+
returnerr;
476+
}
477+
449478
/**
450479
* Synchronously calls each of the listeners registered
451480
* for the event.
@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
466495

467496
// If there is no 'error' event listener then throw.
468497
if(doError){
469-
leter;
470-
if(args.length>0)
471-
er=args[0];
472-
if(erinstanceofError){
473-
try{
474-
constcapture={};
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-
thrower;// Unhandled 'error' event
488-
}
489-
490-
letstringifiedEr;
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-
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
499-
err.context=er;
500-
throwerr;// Unhandled 'error' event
498+
conster=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+
thrower;// Unhandled 'error' event
501502
}
502503

503504
consthandler=events[type];
@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {
584585

585586
// Check for listener leak
586587
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-
constw=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);
596590
}
597591

598592
returntarget;
599593
}
600594

595+
functionwarnMaxListenersExceeded(target,type,existing,m){
596+
existing.warned=true;
597+
// No error code for this since it is a Warning
598+
constw=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+
601605
/**
602606
* Adds a listener to the event emitter.
603607
* @param {string | symbol} type
@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
622626
return_addListener(this,type,listener,true);
623627
};
624628

625-
functiononceWrapper(){
626-
if(!this.fired){
627-
this.target.removeListener(this.type,this.wrapFn);
628-
this.fired=true;
629-
if(arguments.length===0)
630-
returnthis.listener.call(this.target);
631-
returnthis.listener.apply(this.target,arguments);
632-
}
633-
}
634-
635629
function_onceWrap(target,type,listener){
636-
conststate={fired: false,wrapFn: undefined, target, type, listener };
637-
constwrapped=onceWrapper.bind(state);
638-
wrapped.listener=listener;
639-
state.wrapFn=wrapped;
640-
returnwrapped;
630+
letfired=false;
631+
functionwrapper(...args){
632+
if(fired)return;
633+
fired=true;
634+
target.removeListener(type,wrapper);
635+
returnReflectApply(listener,target,args);
636+
}
637+
wrapper.listener=listener;
638+
returnwrapper;
641639
}
642640

643641
/**
@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
689687
if(list===listener||list.listener===listener){
690688
this._eventsCount-=1;
691689

692-
if(this[kShapeMode]){
693-
events[type]=undefined;
694-
}elseif(this._eventsCount===0){
695-
this._events={__proto__: null};
696-
}else{
697-
deleteevents[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;
699696

700697
if(events.removeListener!==undefined)
701698
this.emit('removeListener',type,list.listener||listener);
@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
755752
else
756753
deleteevents[type];
757754
}
758-
this[kShapeMode]=false;
759755
returnthis;
760756
}
761757

@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
768764
this.removeAllListeners('removeListener');
769765
this._events={__proto__: null};
770766
this._eventsCount=0;
771-
this[kShapeMode]=false;
772767
returnthis;
773768
}
774769

@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
868863
* @returns {(string | symbol)[]}
869864
*/
870865
EventEmitter.prototype.eventNames=functioneventNames(){
871-
returnthis._eventsCount>0 ? ReflectOwnKeys(this._events) : [];
866+
if(this._eventsCount===0)
867+
return[];
868+
constevents=this._events;
869+
constnames=[];
870+
for(constkeyofReflectOwnKeys(events)){
871+
// Removed listeners leave the key in place with an `undefined` value.
872+
if(events[key]!==undefined)
873+
ArrayPrototypePush(names,key);
874+
}
875+
returnnames;
872876
};
873877

874878
functionarrayClone(arr){

‎test/fixtures/errors/events_unhandled_error_common_trace.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error: foo:bar
66
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)

‎test/fixtures/errors/events_unhandled_error_nexttick.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)

‎test/fixtures/errors/events_unhandled_error_sameline.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)

‎test/fixtures/errors/events_unhandled_error_subclass.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit 73e1701

Browse files
mcollinaaduh95
authored andcommitted
events: optimize once() and removeListener()
Use a closure instead of a bound state object for the once() wrapper, keep the events object shape in removeListener() by storing undefined instead of deleting keys or reallocating the object, and outline the cold unhandled 'error' path from emit() and the max-listeners warning from _addListener() so both fit within V8 inlining budgets. ee-once +25%, ee-add-remove +12-22%, ee-listen-unique +7-15%, streams/pipe +13.9%. Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64373 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 86bca58 commit 73e1701

5 files changed

Lines changed: 82 additions & 78 deletions

‎lib/events.js‎

Lines changed: 74 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');
8686

8787
constkCapture=Symbol('kCapture');
8888
constkErrorMonitor=Symbol('events.errorMonitor');
89-
constkShapeMode=Symbol('shapeMode');
9089
constkEmitting=Symbol('events.emitting');
9190
constkMaxEventTargetListeners=Symbol('events.maxEventTargetListeners');
9291
constkMaxEventTargetListenersWarned=
@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
335334
this._events===ObjectGetPrototypeOf(this)._events){
336335
this._events={__proto__: null};
337336
this._eventsCount=0;
338-
this[kShapeMode]=false;
339-
}else{
340-
this[kShapeMode]=true;
341337
}
342338

343339
this._maxListeners||=undefined;
@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
446442
returnerr.stack+sep+ArrayPrototypeJoin(ownStack,'\n');
447443
}
448444

445+
functiongetUnhandledErrorException(ee,args){
446+
leter;
447+
if(args.length>0)
448+
er=args[0];
449+
if(erinstanceofError){
450+
try{
451+
constcapture={};
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+
returner;
463+
}
464+
465+
letstringifiedEr;
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+
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
474+
err.context=er;
475+
returnerr;
476+
}
477+
449478
/**
450479
* Synchronously calls each of the listeners registered
451480
* for the event.
@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
466495

467496
// If there is no 'error' event listener then throw.
468497
if(doError){
469-
leter;
470-
if(args.length>0)
471-
er=args[0];
472-
if(erinstanceofError){
473-
try{
474-
constcapture={};
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-
thrower;// Unhandled 'error' event
488-
}
489-
490-
letstringifiedEr;
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-
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
499-
err.context=er;
500-
throwerr;// Unhandled 'error' event
498+
conster=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+
thrower;// Unhandled 'error' event
501502
}
502503

503504
consthandler=events[type];
@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {
584585

585586
// Check for listener leak
586587
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-
constw=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);
596590
}
597591

598592
returntarget;
599593
}
600594

595+
functionwarnMaxListenersExceeded(target,type,existing,m){
596+
existing.warned=true;
597+
// No error code for this since it is a Warning
598+
constw=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+
601605
/**
602606
* Adds a listener to the event emitter.
603607
* @param {string | symbol} type
@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
622626
return_addListener(this,type,listener,true);
623627
};
624628

625-
functiononceWrapper(){
626-
if(!this.fired){
627-
this.target.removeListener(this.type,this.wrapFn);
628-
this.fired=true;
629-
if(arguments.length===0)
630-
returnthis.listener.call(this.target);
631-
returnthis.listener.apply(this.target,arguments);
632-
}
633-
}
634-
635629
function_onceWrap(target,type,listener){
636-
conststate={fired: false,wrapFn: undefined, target, type, listener };
637-
constwrapped=onceWrapper.bind(state);
638-
wrapped.listener=listener;
639-
state.wrapFn=wrapped;
640-
returnwrapped;
630+
letfired=false;
631+
functionwrapper(...args){
632+
if(fired)return;
633+
fired=true;
634+
target.removeListener(type,wrapper);
635+
returnReflectApply(listener,target,args);
636+
}
637+
wrapper.listener=listener;
638+
returnwrapper;
641639
}
642640

643641
/**
@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
689687
if(list===listener||list.listener===listener){
690688
this._eventsCount-=1;
691689

692-
if(this[kShapeMode]){
693-
events[type]=undefined;
694-
}elseif(this._eventsCount===0){
695-
this._events={__proto__: null};
696-
}else{
697-
deleteevents[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;
699696

700697
if(events.removeListener!==undefined)
701698
this.emit('removeListener',type,list.listener||listener);
@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
755752
else
756753
deleteevents[type];
757754
}
758-
this[kShapeMode]=false;
759755
returnthis;
760756
}
761757

@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
768764
this.removeAllListeners('removeListener');
769765
this._events={__proto__: null};
770766
this._eventsCount=0;
771-
this[kShapeMode]=false;
772767
returnthis;
773768
}
774769

@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
868863
* @returns {(string | symbol)[]}
869864
*/
870865
EventEmitter.prototype.eventNames=functioneventNames(){
871-
returnthis._eventsCount>0 ? ReflectOwnKeys(this._events) : [];
866+
if(this._eventsCount===0)
867+
return[];
868+
constevents=this._events;
869+
constnames=[];
870+
for(constkeyofReflectOwnKeys(events)){
871+
// Removed listeners leave the key in place with an `undefined` value.
872+
if(events[key]!==undefined)
873+
ArrayPrototypePush(names,key);
874+
}
875+
returnnames;
872876
};
873877

874878
functionarrayClone(arr){

‎test/fixtures/errors/events_unhandled_error_common_trace.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error: foo:bar
66
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)

‎test/fixtures/errors/events_unhandled_error_nexttick.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)

‎test/fixtures/errors/events_unhandled_error_sameline.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)

‎test/fixtures/errors/events_unhandled_error_subclass.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 73e1701

Browse files
mcollinaaduh95
authored andcommitted
events: optimize once() and removeListener()
Use a closure instead of a bound state object for the once() wrapper, keep the events object shape in removeListener() by storing undefined instead of deleting keys or reallocating the object, and outline the cold unhandled 'error' path from emit() and the max-listeners warning from _addListener() so both fit within V8 inlining budgets. ee-once +25%, ee-add-remove +12-22%, ee-listen-unique +7-15%, streams/pipe +13.9%. Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64373 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 86bca58 commit 73e1701

5 files changed

Lines changed: 82 additions & 78 deletions

‎lib/events.js‎

Lines changed: 74 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');
8686

8787
constkCapture=Symbol('kCapture');
8888
constkErrorMonitor=Symbol('events.errorMonitor');
89-
constkShapeMode=Symbol('shapeMode');
9089
constkEmitting=Symbol('events.emitting');
9190
constkMaxEventTargetListeners=Symbol('events.maxEventTargetListeners');
9291
constkMaxEventTargetListenersWarned=
@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
335334
this._events===ObjectGetPrototypeOf(this)._events){
336335
this._events={__proto__: null};
337336
this._eventsCount=0;
338-
this[kShapeMode]=false;
339-
}else{
340-
this[kShapeMode]=true;
341337
}
342338

343339
this._maxListeners||=undefined;
@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
446442
returnerr.stack+sep+ArrayPrototypeJoin(ownStack,'\n');
447443
}
448444

445+
functiongetUnhandledErrorException(ee,args){
446+
leter;
447+
if(args.length>0)
448+
er=args[0];
449+
if(erinstanceofError){
450+
try{
451+
constcapture={};
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+
returner;
463+
}
464+
465+
letstringifiedEr;
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+
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
474+
err.context=er;
475+
returnerr;
476+
}
477+
449478
/**
450479
* Synchronously calls each of the listeners registered
451480
* for the event.
@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
466495

467496
// If there is no 'error' event listener then throw.
468497
if(doError){
469-
leter;
470-
if(args.length>0)
471-
er=args[0];
472-
if(erinstanceofError){
473-
try{
474-
constcapture={};
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-
thrower;// Unhandled 'error' event
488-
}
489-
490-
letstringifiedEr;
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-
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
499-
err.context=er;
500-
throwerr;// Unhandled 'error' event
498+
conster=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+
thrower;// Unhandled 'error' event
501502
}
502503

503504
consthandler=events[type];
@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {
584585

585586
// Check for listener leak
586587
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-
constw=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);
596590
}
597591

598592
returntarget;
599593
}
600594

595+
functionwarnMaxListenersExceeded(target,type,existing,m){
596+
existing.warned=true;
597+
// No error code for this since it is a Warning
598+
constw=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+
601605
/**
602606
* Adds a listener to the event emitter.
603607
* @param {string | symbol} type
@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
622626
return_addListener(this,type,listener,true);
623627
};
624628

625-
functiononceWrapper(){
626-
if(!this.fired){
627-
this.target.removeListener(this.type,this.wrapFn);
628-
this.fired=true;
629-
if(arguments.length===0)
630-
returnthis.listener.call(this.target);
631-
returnthis.listener.apply(this.target,arguments);
632-
}
633-
}
634-
635629
function_onceWrap(target,type,listener){
636-
conststate={fired: false,wrapFn: undefined, target, type, listener };
637-
constwrapped=onceWrapper.bind(state);
638-
wrapped.listener=listener;
639-
state.wrapFn=wrapped;
640-
returnwrapped;
630+
letfired=false;
631+
functionwrapper(...args){
632+
if(fired)return;
633+
fired=true;
634+
target.removeListener(type,wrapper);
635+
returnReflectApply(listener,target,args);
636+
}
637+
wrapper.listener=listener;
638+
returnwrapper;
641639
}
642640

643641
/**
@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
689687
if(list===listener||list.listener===listener){
690688
this._eventsCount-=1;
691689

692-
if(this[kShapeMode]){
693-
events[type]=undefined;
694-
}elseif(this._eventsCount===0){
695-
this._events={__proto__: null};
696-
}else{
697-
deleteevents[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;
699696

700697
if(events.removeListener!==undefined)
701698
this.emit('removeListener',type,list.listener||listener);
@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
755752
else
756753
deleteevents[type];
757754
}
758-
this[kShapeMode]=false;
759755
returnthis;
760756
}
761757

@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
768764
this.removeAllListeners('removeListener');
769765
this._events={__proto__: null};
770766
this._eventsCount=0;
771-
this[kShapeMode]=false;
772767
returnthis;
773768
}
774769

@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
868863
* @returns {(string | symbol)[]}
869864
*/
870865
EventEmitter.prototype.eventNames=functioneventNames(){
871-
returnthis._eventsCount>0 ? ReflectOwnKeys(this._events) : [];
866+
if(this._eventsCount===0)
867+
return[];
868+
constevents=this._events;
869+
constnames=[];
870+
for(constkeyofReflectOwnKeys(events)){
871+
// Removed listeners leave the key in place with an `undefined` value.
872+
if(events[key]!==undefined)
873+
ArrayPrototypePush(names,key);
874+
}
875+
returnnames;
872876
};
873877

874878
functionarrayClone(arr){

‎test/fixtures/errors/events_unhandled_error_common_trace.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error: foo:bar
66
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)

‎test/fixtures/errors/events_unhandled_error_nexttick.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)

‎test/fixtures/errors/events_unhandled_error_sameline.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)

‎test/fixtures/errors/events_unhandled_error_subclass.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 73e1701

Browse files
mcollinaaduh95
authored andcommitted
events: optimize once() and removeListener()
Use a closure instead of a bound state object for the once() wrapper, keep the events object shape in removeListener() by storing undefined instead of deleting keys or reallocating the object, and outline the cold unhandled 'error' path from emit() and the max-listeners warning from _addListener() so both fit within V8 inlining budgets. ee-once +25%, ee-add-remove +12-22%, ee-listen-unique +7-15%, streams/pipe +13.9%. Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64373 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 86bca58 commit 73e1701

5 files changed

Lines changed: 82 additions & 78 deletions

‎lib/events.js‎

Lines changed: 74 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');
8686

8787
constkCapture=Symbol('kCapture');
8888
constkErrorMonitor=Symbol('events.errorMonitor');
89-
constkShapeMode=Symbol('shapeMode');
9089
constkEmitting=Symbol('events.emitting');
9190
constkMaxEventTargetListeners=Symbol('events.maxEventTargetListeners');
9291
constkMaxEventTargetListenersWarned=
@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
335334
this._events===ObjectGetPrototypeOf(this)._events){
336335
this._events={__proto__: null};
337336
this._eventsCount=0;
338-
this[kShapeMode]=false;
339-
}else{
340-
this[kShapeMode]=true;
341337
}
342338

343339
this._maxListeners||=undefined;
@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
446442
returnerr.stack+sep+ArrayPrototypeJoin(ownStack,'\n');
447443
}
448444

445+
functiongetUnhandledErrorException(ee,args){
446+
leter;
447+
if(args.length>0)
448+
er=args[0];
449+
if(erinstanceofError){
450+
try{
451+
constcapture={};
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+
returner;
463+
}
464+
465+
letstringifiedEr;
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+
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
474+
err.context=er;
475+
returnerr;
476+
}
477+
449478
/**
450479
* Synchronously calls each of the listeners registered
451480
* for the event.
@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
466495

467496
// If there is no 'error' event listener then throw.
468497
if(doError){
469-
leter;
470-
if(args.length>0)
471-
er=args[0];
472-
if(erinstanceofError){
473-
try{
474-
constcapture={};
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-
thrower;// Unhandled 'error' event
488-
}
489-
490-
letstringifiedEr;
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-
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
499-
err.context=er;
500-
throwerr;// Unhandled 'error' event
498+
conster=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+
thrower;// Unhandled 'error' event
501502
}
502503

503504
consthandler=events[type];
@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {
584585

585586
// Check for listener leak
586587
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-
constw=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);
596590
}
597591

598592
returntarget;
599593
}
600594

595+
functionwarnMaxListenersExceeded(target,type,existing,m){
596+
existing.warned=true;
597+
// No error code for this since it is a Warning
598+
constw=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+
601605
/**
602606
* Adds a listener to the event emitter.
603607
* @param {string | symbol} type
@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
622626
return_addListener(this,type,listener,true);
623627
};
624628

625-
functiononceWrapper(){
626-
if(!this.fired){
627-
this.target.removeListener(this.type,this.wrapFn);
628-
this.fired=true;
629-
if(arguments.length===0)
630-
returnthis.listener.call(this.target);
631-
returnthis.listener.apply(this.target,arguments);
632-
}
633-
}
634-
635629
function_onceWrap(target,type,listener){
636-
conststate={fired: false,wrapFn: undefined, target, type, listener };
637-
constwrapped=onceWrapper.bind(state);
638-
wrapped.listener=listener;
639-
state.wrapFn=wrapped;
640-
returnwrapped;
630+
letfired=false;
631+
functionwrapper(...args){
632+
if(fired)return;
633+
fired=true;
634+
target.removeListener(type,wrapper);
635+
returnReflectApply(listener,target,args);
636+
}
637+
wrapper.listener=listener;
638+
returnwrapper;
641639
}
642640

643641
/**
@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
689687
if(list===listener||list.listener===listener){
690688
this._eventsCount-=1;
691689

692-
if(this[kShapeMode]){
693-
events[type]=undefined;
694-
}elseif(this._eventsCount===0){
695-
this._events={__proto__: null};
696-
}else{
697-
deleteevents[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;
699696

700697
if(events.removeListener!==undefined)
701698
this.emit('removeListener',type,list.listener||listener);
@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
755752
else
756753
deleteevents[type];
757754
}
758-
this[kShapeMode]=false;
759755
returnthis;
760756
}
761757

@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
768764
this.removeAllListeners('removeListener');
769765
this._events={__proto__: null};
770766
this._eventsCount=0;
771-
this[kShapeMode]=false;
772767
returnthis;
773768
}
774769

@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
868863
* @returns {(string | symbol)[]}
869864
*/
870865
EventEmitter.prototype.eventNames=functioneventNames(){
871-
returnthis._eventsCount>0 ? ReflectOwnKeys(this._events) : [];
866+
if(this._eventsCount===0)
867+
return[];
868+
constevents=this._events;
869+
constnames=[];
870+
for(constkeyofReflectOwnKeys(events)){
871+
// Removed listeners leave the key in place with an `undefined` value.
872+
if(events[key]!==undefined)
873+
ArrayPrototypePush(names,key);
874+
}
875+
returnnames;
872876
};
873877

874878
functionarrayClone(arr){

‎test/fixtures/errors/events_unhandled_error_common_trace.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error: foo:bar
66
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)

‎test/fixtures/errors/events_unhandled_error_nexttick.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)

‎test/fixtures/errors/events_unhandled_error_sameline.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)

‎test/fixtures/errors/events_unhandled_error_subclass.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit 73e1701

Browse files
mcollinaaduh95
authored andcommitted
events: optimize once() and removeListener()
Use a closure instead of a bound state object for the once() wrapper, keep the events object shape in removeListener() by storing undefined instead of deleting keys or reallocating the object, and outline the cold unhandled 'error' path from emit() and the max-listeners warning from _addListener() so both fit within V8 inlining budgets. ee-once +25%, ee-add-remove +12-22%, ee-listen-unique +7-15%, streams/pipe +13.9%. Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64373 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 86bca58 commit 73e1701

5 files changed

Lines changed: 82 additions & 78 deletions

‎lib/events.js‎

Lines changed: 74 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');
8686

8787
constkCapture=Symbol('kCapture');
8888
constkErrorMonitor=Symbol('events.errorMonitor');
89-
constkShapeMode=Symbol('shapeMode');
9089
constkEmitting=Symbol('events.emitting');
9190
constkMaxEventTargetListeners=Symbol('events.maxEventTargetListeners');
9291
constkMaxEventTargetListenersWarned=
@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
335334
this._events===ObjectGetPrototypeOf(this)._events){
336335
this._events={__proto__: null};
337336
this._eventsCount=0;
338-
this[kShapeMode]=false;
339-
}else{
340-
this[kShapeMode]=true;
341337
}
342338

343339
this._maxListeners||=undefined;
@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
446442
returnerr.stack+sep+ArrayPrototypeJoin(ownStack,'\n');
447443
}
448444

445+
functiongetUnhandledErrorException(ee,args){
446+
leter;
447+
if(args.length>0)
448+
er=args[0];
449+
if(erinstanceofError){
450+
try{
451+
constcapture={};
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+
returner;
463+
}
464+
465+
letstringifiedEr;
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+
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
474+
err.context=er;
475+
returnerr;
476+
}
477+
449478
/**
450479
* Synchronously calls each of the listeners registered
451480
* for the event.
@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
466495

467496
// If there is no 'error' event listener then throw.
468497
if(doError){
469-
leter;
470-
if(args.length>0)
471-
er=args[0];
472-
if(erinstanceofError){
473-
try{
474-
constcapture={};
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-
thrower;// Unhandled 'error' event
488-
}
489-
490-
letstringifiedEr;
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-
consterr=newERR_UNHANDLED_ERROR(stringifiedEr);
499-
err.context=er;
500-
throwerr;// Unhandled 'error' event
498+
conster=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+
thrower;// Unhandled 'error' event
501502
}
502503

503504
consthandler=events[type];
@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {
584585

585586
// Check for listener leak
586587
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-
constw=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);
596590
}
597591

598592
returntarget;
599593
}
600594

595+
functionwarnMaxListenersExceeded(target,type,existing,m){
596+
existing.warned=true;
597+
// No error code for this since it is a Warning
598+
constw=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+
601605
/**
602606
* Adds a listener to the event emitter.
603607
* @param {string | symbol} type
@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
622626
return_addListener(this,type,listener,true);
623627
};
624628

625-
functiononceWrapper(){
626-
if(!this.fired){
627-
this.target.removeListener(this.type,this.wrapFn);
628-
this.fired=true;
629-
if(arguments.length===0)
630-
returnthis.listener.call(this.target);
631-
returnthis.listener.apply(this.target,arguments);
632-
}
633-
}
634-
635629
function_onceWrap(target,type,listener){
636-
conststate={fired: false,wrapFn: undefined, target, type, listener };
637-
constwrapped=onceWrapper.bind(state);
638-
wrapped.listener=listener;
639-
state.wrapFn=wrapped;
640-
returnwrapped;
630+
letfired=false;
631+
functionwrapper(...args){
632+
if(fired)return;
633+
fired=true;
634+
target.removeListener(type,wrapper);
635+
returnReflectApply(listener,target,args);
636+
}
637+
wrapper.listener=listener;
638+
returnwrapper;
641639
}
642640

643641
/**
@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
689687
if(list===listener||list.listener===listener){
690688
this._eventsCount-=1;
691689

692-
if(this[kShapeMode]){
693-
events[type]=undefined;
694-
}elseif(this._eventsCount===0){
695-
this._events={__proto__: null};
696-
}else{
697-
deleteevents[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;
699696

700697
if(events.removeListener!==undefined)
701698
this.emit('removeListener',type,list.listener||listener);
@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
755752
else
756753
deleteevents[type];
757754
}
758-
this[kShapeMode]=false;
759755
returnthis;
760756
}
761757

@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
768764
this.removeAllListeners('removeListener');
769765
this._events={__proto__: null};
770766
this._eventsCount=0;
771-
this[kShapeMode]=false;
772767
returnthis;
773768
}
774769

@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
868863
* @returns {(string | symbol)[]}
869864
*/
870865
EventEmitter.prototype.eventNames=functioneventNames(){
871-
returnthis._eventsCount>0 ? ReflectOwnKeys(this._events) : [];
866+
if(this._eventsCount===0)
867+
return[];
868+
constevents=this._events;
869+
constnames=[];
870+
for(constkeyofReflectOwnKeys(events)){
871+
// Removed listeners leave the key in place with an `undefined` value.
872+
if(events[key]!==undefined)
873+
ArrayPrototypePush(names,key);
874+
}
875+
returnnames;
872876
};
873877

874878
functionarrayClone(arr){

‎test/fixtures/errors/events_unhandled_error_common_trace.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error: foo:bar
66
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)

‎test/fixtures/errors/events_unhandled_error_nexttick.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)

‎test/fixtures/errors/events_unhandled_error_sameline.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)

‎test/fixtures/errors/events_unhandled_error_subclass.snapshot‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
node:events:<line>
2-
throw er; // Unhandled 'error' event
3-
^
2+
throw er; // Unhandled 'error' event
3+
^
44

55
Error
66
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)

0 commit comments

Comments
 (0)