Last active
August 10, 2020 15:46
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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