UNPKG

awv3

Version:
178 lines (156 loc) 6.15 kB
export default class Events { constructor() { this._callbacks = undefined; this._inspectors = undefined; } once(type, callback) { this.on(type, callback, {remove: true}); } onFirst(type, callback) { this.on(type, callback, {priority: 1}); } onLast(type, callback) { this.on(type, callback, {priority: -1}); } on(arg1 = {}, arg2 = undefined, options = {}) { if (Array.isArray(arg1)) { // (Array of types) & callback for (let type of arg1) this.on(type, arg2, options); } else if (typeof arg1 === 'object') { // Object with 'Type: Callback' pairs if (typeof(arg2) === 'object') options = arg2; for (let [key, callback] of Object.entries(arg1)) if (typeof callback === 'function') this.on(key, callback, options); } else if (typeof arg1 === 'string' && typeof arg2 === 'function') { // Type & Callback let type = arg1, callback = arg2; let {remove = false, priority = 0, sync = false} = options; if (!this._callbacks) this._callbacks = {}; let listeners = this._callbacks[type]; if (!listeners) listeners = this._callbacks[type] = []; // note: do not add duplicate callbacks if (listeners.indexOf(callback) < 0) { callback.remove = remove; callback.sync = sync; callback.priority = priority; listeners.push(callback); listeners.sort((a, b) => b.priority - a.priority); if (this._inspectors) { for (let inspector of this._inspectors) inspector({ action: 'Add', type, callback }); } } } return this; } inspect(callback) { if (!this._inspectors) this._inspectors = []; this._inspectors.push(callback); } removeListener(types, callback) { if (!Array.isArray(types) && typeof types === 'object' && callback === undefined) { // Object of 'Type: Callback' for (let [type, callback] of Object.entries(types)) this.removeListener(type, callback); return this; } if (!this._callbacks) this._callbacks = {}; types = Array.isArray(types) ? types : [types]; for (let type of types) { let listeners = this._callbacks[type]; if (!listeners) continue; if (!callback) { delete this._callbacks[type]; if (this._inspectors) { for (let inspector of this._inspectors) inspector({ action: 'Remove', type, callback: undefined }); } } else { let index = listeners.indexOf(callback); index > -1 && listeners.splice(index, 1); if (this._inspectors) { for (let inspector of this._inspectors) inspector({ action: 'Remove', type, callback }); } } } return this; } removeListeners() { this._callbacks = undefined; } removeInspectors() { this._inspectors = undefined; } emit(type, ...args) { if (!this._callbacks) this._callbacks = {}; let listeners = this._callbacks[type]; //clone listeners array to ensure safe callbacks removal listeners = listeners ? [...listeners] : []; //call all synchronous events first; for (let listener of listeners) if (listener.sync) { if (listener.remove) this.removeListener(type, listener); listener.call(this, ...args); } //call all asynchronous events in promise chain let sequence = Promise.resolve(); for (let listener of listeners) if (!listener.sync) { sequence = sequence.then(() => { if (listener.remove) this.removeListener(type, listener); return listener.call(this, ...args); }); } return sequence; } //TODO: does it work properly? //what about case when listeners = [] bubble(type, ...args) { if (!this._callbacks) this._callbacks = {}; let listeners = this._callbacks[type]; if (listeners) { return this.emit(type, ...args); } else if (this.parent) { // No listener found, just bubble up ... return this.parent.bubble(type, ...args); } return Promise.resolve(); } findListener(type) { if (!this._callbacks) this._callbacks = {}; let listeners = this._callbacks[type]; if (listeners) { return this; } else if (this.parent) { // No listener found, just bubble up ... return this.parent.find(type); } return undefined; } hasListener(type, callback = undefined) { if (!this._callbacks) this._callbacks = {}; let listener = this._callbacks[type]; return !!listener && (!callback || callback === listener); } static mixin(object, handlers = null) { object.hasListener = Events.prototype.hasListener; object.on = Events.prototype.on; object.once = Events.prototype.once; object.onFirst = Events.prototype.onFirst; object.onLast = Events.prototype.onLast; object.inspect = Events.prototype.inspect; object.removeListener = Events.prototype.removeListener; object.removeListeners = Events.prototype.removeListeners; object.removeInspectors = Events.prototype.removeInspectors; object.emit = Events.prototype.emit; object.bubble = Events.prototype.bubble; object.findListener = Events.prototype.findListener; if (handlers) object.on.bind(object)(handlers); } }