Skip to content

Instantly share code, notes, and snippets.

@thejhh
Last active September 19, 2020 09:55
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 thejhh/9d914b02582b56b075bfa6279d6473c5 to your computer and use it in GitHub Desktop.
Save thejhh/9d914b02582b56b075bfa6279d6473c5 to your computer and use it in GitHub Desktop.
Observer.ts
// Copyright 2020 Jaakko Heusala <jheusala@iki.fi>
// Licence: MIT
import {filter, forEach, has} from "../../modules/lodash";
export interface ObserverCallback<EventName extends keyof any> {
(event: EventName) : void;
}
export interface ObserverDestructor {
() : void;
}
export type ObserverCallbackArray<EventName extends keyof any> = Array<ObserverCallback<EventName>>;
export type ObserverRecord<EventName extends keyof any> = Record<EventName, ObserverCallbackArray<EventName> >;
/**
* This is a simple observer implementation for implementing synchronous in-process events for a local service.
*
* You'll use it like:
*
* ```
* enum FooEvent {
* CHANGED = "FooService:changed"
* }
*
* class FooService {
*
* private static _data : any;
* private static _observer : Observer<FooEvent> = {};
*
* public static on (name : FooEvent, callback) : ObserverDestructor {
* return this._observer.listenEvent(name, callback);
* }
*
* public static refreshData () {
*
* HttpService.doSomething().then((response) => {
*
* this._data = response.data;
*
* this._observer.triggerEvent(FooEvent.CHANGED);
*
* }).catch(err => {
* console.error('Error: ', err);
* });
*
* }
*
* }
* ```
*
*/
export class Observer<EventName extends keyof any> {
private _name : string;
private _callbacks : ObserverRecord<EventName>;
/**
*
* @param name You can name this observer, so that you know where it is used.
*/
constructor(name: string) {
this._name = name;
this._callbacks = {} as ObserverRecord<EventName>;
}
/**
* Destroy the observer data. Stop using this object after you use destroy.
*/
public destroy () {
// @ts-ignore
this._name = undefined;
// @ts-ignore
this._callbacks = undefined;
}
/**
* Check if eventName has listeners.
*
* @param eventName
*/
public hasCallbacks (eventName : EventName) : boolean {
return has(this._callbacks, eventName);
}
/**
* Trigger an event
*
* @param eventName
*/
public triggerEvent (eventName : EventName) {
if (!this.hasCallbacks(eventName)) {
console.warn(`Warning! The observer for "${this._name}" did not have anything listening "${eventName}"`);
return;
}
const callbacks = this._callbacks[eventName];
forEach(callbacks, callback => {
try {
callback(eventName);
} catch( e ) {
console.error(`Observer "${this._name}" and the event handler for "${eventName}" returned an exception: `, e);
}
});
}
/**
* Start listening events.
*
* Returns destructor function.
*
* @param eventName
* @param callback
*/
public listenEvent (eventName : EventName, callback : ObserverCallback<EventName> ) : ObserverDestructor {
if (!this.hasCallbacks(eventName)) {
this._callbacks[eventName] = [ callback ];
} else {
this._callbacks[eventName].push( callback );
}
return () => this.removeListener(eventName, callback);
}
/**
* Removes the first found listener callback for eventName
*
* @param eventName
* @param callback
*/
public removeListener (eventName : EventName, callback: ObserverCallback<EventName>) : void {
if (!this.hasCallbacks(eventName)) {
console.warn(`Warning! Could not remove callback since the observer for "${this._name}" did not have anything listening "${eventName}"`);
return;
}
let removedOnce = false;
this._callbacks[eventName] = filter(this._callbacks[eventName], item => {
if ( !removedOnce && item === callback ) {
removedOnce = true;
return false;
}
return true;
});
if (this._callbacks[eventName].length === 0) {
delete this._callbacks[eventName];
}
if (!removedOnce) {
console.warn(`Warning! Could not remove the callback since the observer for "${this._name}" did not have that callback`);
return;
}
}
}
export default Observer;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment