UNPKG

publisher-subscriber-pattern

Version:

Publisher subscriber pattern that can be used with different event emitters including browser window

123 lines (99 loc) 3.66 kB
import { EventCallback, EventData, EventName, PublisherProps, SubscriberInstance, SubscriptionFunctions, UnsubscribeFunction, isValidEmitter } from './_types'; import { Subscriber } from './subscriber'; export class Publisher { private eventData: Map< EventName, EventData>; private addEventListener: SubscriptionFunctions; private removeEventListener: SubscriptionFunctions; constructor (...args: PublisherProps) { const [ emitterInstance, addListenerMethodName, removeListenerMethodName ] = args; if (!isValidEmitter(emitterInstance, addListenerMethodName, removeListenerMethodName)) { throw new Error('Publisher received incorrect arguments'); } this.eventData = new Map([]); this.addEventListener = // @ts-ignore ( emitterInstance[ addListenerMethodName ] as SubscriptionFunctions).bind(emitterInstance); this.removeEventListener = // @ts-ignore ( emitterInstance[ removeListenerMethodName ] as SubscriptionFunctions).bind(emitterInstance); } public subscribe = ( eventName: EventName, eventCallback: EventCallback, subscriberInstance?: SubscriberInstance ): () => void => { const subscriber = new Subscriber(eventCallback, subscriberInstance); let eventData = this.getEventData(eventName); if (!eventData) { eventData = this.eventData .set( eventName, [ this.buildInformSubscribers(eventName), [] ]) .get(eventName) as EventData; this.observeEvent(eventName, eventData); } eventData[ 1 ].push(subscriber); return this.unsubscribe(eventName, subscriber); } public unsubscribeAll = (): void => { Array.from(this.eventData.keys()).forEach(eventName => { this.unobserveEvent(eventName); }); } public eventSubscribersCount = (eventName: EventName): number => { return (this.getSubscribers(eventName) || []).length; } public subscribersCount = (): number => { return Array.from(this.eventData.values()).reduce((count = 0, eventData) => { count += eventData[ 1 ].length; return count; }, 0); } private getEventData (eventName: EventName): EventData | undefined { return this.eventData.get(eventName); } private getEventCallback (eventName: EventName): EventCallback | undefined { return (this.getEventData(eventName) || [])[ 0 ]; } private getSubscribers (eventName: EventName): Subscriber[] | undefined { return (this.getEventData(eventName) || [])[ 1 ]; } private buildInformSubscribers = (eventName: EventName): EventCallback => ( (event: Event): void => { (this.getSubscribers(eventName) || []) .forEach(subscriber => subscriber.eventCallback(event)); } ) private unsubscribe = (eventName: EventName, subscriber: Subscriber): UnsubscribeFunction => { return (): void => { const subscribersArray = (this.getEventData(eventName) || [])[ 1 ]; if (subscribersArray) { subscribersArray .splice(subscribersArray.findIndex(item => item === subscriber), 1); if (!subscribersArray.length) { this.unobserveEvent(eventName); } } return; }; } private observeEvent = (eventName: EventName, eventData?: EventData): void => { const eventCallback = eventData ? eventData[ 0 ] : this.getEventCallback(eventName); if (eventCallback) { this.addEventListener(eventName, eventCallback); } } private unobserveEvent = (eventName: EventName): void => { this.removeEventListener(eventName, this.getEventCallback(eventName) as EventCallback); this.eventData.delete(eventName); } }