lib/event_handler.js (79 lines of code) (raw):
/**
* EventHandler callback
* @typedef {(event: {[key: string]: any}): void} EventHandlerCallbackType
*/
const sleep = (t) => new Promise((r) => setTimeout(() => r(), t));
/**
* Inherited class for RealtimeAPI and RealtimeClient
* Adds basic event handling
* @class
*/
export class RealtimeEventHandler {
/**
* Create a new RealtimeEventHandler instance
* @returns {RealtimeEventHandler}
*/
constructor() {
this.eventHandlers = {};
this.nextEventHandlers = {};
}
/**
* Clears all event handlers
* @returns {true}
*/
clearEventHandlers() {
this.eventHandlers = {};
this.nextEventHandlers = {};
return true;
}
/**
* Listen to specific events
* @param {string} eventName The name of the event to listen to
* @param {EventHandlerCallbackType} callback Code to execute on event
* @returns {EventHandlerCallbackType}
*/
on(eventName, callback) {
this.eventHandlers[eventName] = this.eventHandlers[eventName] || [];
this.eventHandlers[eventName].push(callback);
return callback;
}
/**
* Listen for the next event of a specified type
* @param {string} eventName The name of the event to listen to
* @param {EventHandlerCallbackType} callback Code to execute on event
* @returns {EventHandlerCallbackType}
*/
onNext(eventName, callback) {
this.nextEventHandlers[eventName] = this.nextEventHandlers[eventName] || [];
this.nextEventHandlers[eventName].push(callback);
return callback;
}
/**
* Turns off event listening for specific events
* Calling without a callback will remove all listeners for the event
* @param {string} eventName
* @param {EventHandlerCallbackType} [callback]
* @returns {true}
*/
off(eventName, callback) {
const handlers = this.eventHandlers[eventName] || [];
if (callback) {
const index = handlers.indexOf(callback);
if (index === -1) {
throw new Error(
`Could not turn off specified event listener for "${eventName}": not found as a listener`,
);
}
handlers.splice(index, 1);
} else {
delete this.eventHandlers[eventName];
}
return true;
}
/**
* Turns off event listening for the next event of a specific type
* Calling without a callback will remove all listeners for the next event
* @param {string} eventName
* @param {EventHandlerCallbackType} [callback]
* @returns {true}
*/
offNext(eventName, callback) {
const nextHandlers = this.nextEventHandlers[eventName] || [];
if (callback) {
const index = nextHandlers.indexOf(callback);
if (index === -1) {
throw new Error(
`Could not turn off specified next event listener for "${eventName}": not found as a listener`,
);
}
nextHandlers.splice(index, 1);
} else {
delete this.nextEventHandlers[eventName];
}
return true;
}
/**
* Waits for next event of a specific type and returns the payload
* @param {string} eventName
* @param {number|null} [timeout]
* @returns {Promise<{[key: string]: any}|null>}
*/
async waitForNext(eventName, timeout = null) {
const t0 = Date.now();
let nextEvent;
this.onNext(eventName, (event) => (nextEvent = event));
while (!nextEvent) {
if (timeout) {
const t1 = Date.now();
if (t1 - t0 > timeout) {
return null;
}
}
await sleep(1);
}
return nextEvent;
}
/**
* Executes all events in the order they were added, with .on() event handlers executing before .onNext() handlers
* @param {string} eventName
* @param {any} event
* @returns {true}
*/
dispatch(eventName, event) {
const handlers = [].concat(this.eventHandlers[eventName] || []);
for (const handler of handlers) {
handler(event);
}
const nextHandlers = [].concat(this.nextEventHandlers[eventName] || []);
for (const nextHandler of nextHandlers) {
nextHandler(event);
}
delete this.nextEventHandlers[eventName];
return true;
}
}