Skip to content

Instantly share code, notes, and snippets.

@broskisworld
Last active August 10, 2020 15:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save broskisworld/7c9d77dcebc3d8f605cc2cf1e326a335 to your computer and use it in GitHub Desktop.
Save broskisworld/7c9d77dcebc3d8f605cc2cf1e326a335 to your computer and use it in GitHub Desktop.
Subclass of the built-in Node.js EventEmitter, which allows events to be canceled, or ignored ahead of time.
// class EventEmitterCancelable
//
// EventEmitterCancelable is a subclass of the built-in Node.js EventEmitter
// class. Three additional functions are exposed: cancelActiveEvent(),
// ignoreNext(e), and cancelActiveEvent().
//
// During an 'event' listener callback, the listener can cancel further callback
// processing by calling either cancelActiveEvent() or cancelEvent('event').
// This is similar to stopImmediatePropogation() from the Event class.
//
// Calling ignoreNext('event'), or cancelEvent('event') from outside of an
// 'event' callback, will force the next 'event' emit to be ignored. If
// an 'event' emit is currently being processed and ignoreNext('event') is
// called, event propagation will continue as normal.
'use strict'
const EventEmitter = require('events');
class EventEmitterCancelable extends EventEmitter {
canceledEvents;
activeEvent;
constructor() {
super();
this.canceledEvents = [];
this.activeEvent = null;
}
// cancelActiveEvent()
//
// If event processing is active, no further listeners will be called.
cancelActiveEvent() {
this.activeEvent = null;
}
// ignoreNext()
//
// Ignore the next time that the event e is fired. If the event is currently
// processing, the remaining listeners will be alerted to the event.
ignoreNext(e) {
this.canceledEvents.push(e);
}
// cancelEvent(eventToCancel)
//
// When called while an event is currently being processed, any remaining
// listeners will not be alerted. Otherwise, the next event emit() of that
// event type will simply be ignored.
cancelEvent(e) {
if(this.activeEvent === e)
this.activeEvent = null;
else
this.canceledEvents.push(e);
}
emit(type, ...args) {
for(let i = 0; i < this.canceledEvents.length; i++) {
if(this.canceledEvents[i] === type) {
this.canceledEvents.splice(i);
return false;
break;
}
}
let doError = (type === 'error');
const events = this._events;
if (events !== undefined) {
if (doError && events[kErrorMonitor] !== undefined)
this.emit(kErrorMonitor, ...args);
doError = (doError && events.error === undefined);
} else if (!doError)
return false;
// If there is no 'error' event listener then throw.
if (doError) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
// eslint-disable-next-line no-restricted-syntax
Error.captureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
value: enhanceStackTrace.bind(this, er, capture),
configurable: true
});
} catch {}
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}
let stringifiedEr;
const { inspect } = require('internal/util/inspect');
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}
// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
}
const handler = events[type];
if (handler === undefined)
return false;
if (typeof handler === 'function') {
// No cancel check, as there is only one handler
const result = Reflect.apply(handler, this, args);
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty
if (result !== undefined && result !== null) {
addCatch(this, result, type, args);
}
} else {
this.activeEvent = type;
const len = handler.length;
const listeners = Array.from(handler);
for (let i = 0; i < len; ++i) {
if(!this.activeEvent) {
return false;
break;
}
const result = Reflect.apply(listeners[i], this, args);
// We check if result is undefined first because that
// is the most common case so we do not pay any perf
// penalty.
// This code is duplicated because extracting it away
// would make it non-inlineable.
if (result !== undefined && result !== null) {
addCatch(this, result, type, args);
}
}
}
return true;
}
}
module.exports = EventEmitterCancelable;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment