react-native-unit-components
Version:
Unit React Native components
107 lines (89 loc) • 2.99 kB
text/typescript
import type { EventMap, SharedEvent } from './EventBus.types';
// Type-safe callback that receives the correct data type for the event key
export type EventCallback<K extends keyof EventMap> = (data: EventMap[K]) => void;
class EventBus {
private debug = false;
private listeners = new Map<SharedEvent['key'], Set<(data: unknown) => void>>();
private eventQueue: SharedEvent[] = [];
private isProcessing = false;
/**
* Subscribe to an event
* @returns Unsubscribe function
*/
on<K extends keyof EventMap>(eventKey: K, callback: EventCallback<K>): () => void {
if (!this.listeners.has(eventKey)) {
this.listeners.set(eventKey, new Set());
}
this.listeners.get(eventKey)!.add(callback as (data: unknown) => void);
if (this.debug) {
console.log(`[EventBus] Subscribed to: ${eventKey} (${this.listenerCount(eventKey)} listeners)`);
}
// Return unsubscribe function
return () => {
const callbacks = this.listeners.get(eventKey);
if (callbacks) {
callbacks.delete(callback as (data: unknown) => void);
if (callbacks.size === 0) {
this.listeners.delete(eventKey);
}
}
};
}
/**
* Emit an event - queues it for processing to ensure all events are delivered
*/
emit<K extends keyof EventMap>(eventKey: K, data: EventMap[K]): void {
const event = { key: eventKey, data } as SharedEvent;
if (this.debug) {
console.log(`[EventBus] Emitting: ${eventKey} (${this.listenerCount(eventKey)} listeners)`);
}
this.eventQueue.push(event);
this.processQueue();
}
/**
* Process events from queue sequentially
* This ensures rapid successive events are all delivered
*/
private processQueue(): void {
if (this.isProcessing || this.eventQueue.length === 0) {
return;
}
this.isProcessing = true;
// Process all queued events synchronously to ensure immediate delivery
while (this.eventQueue.length > 0) {
const event = this.eventQueue.shift()!;
const callbacks = this.listeners.get(event.key);
if (callbacks && callbacks.size > 0) {
// Process all listeners for this event
callbacks.forEach(callback => {
try {
callback(event.data);
} catch (error) {
console.error(`Error in event listener for ${event.key}:`, error);
}
});
}
}
this.isProcessing = false;
}
/**
* Remove all listeners for an event
*/
off<K extends keyof EventMap>(eventKey: K): void {
this.listeners.delete(eventKey);
}
/**
* Get number of listeners for an event (useful for debugging)
*/
listenerCount<K extends keyof EventMap>(eventKey: K): number {
return this.listeners.get(eventKey)?.size ?? 0;
}
/**
* Enable or disable debug logging
*/
setDebug(enabled: boolean): void {
this.debug = enabled;
}
}
// Singleton instance
export const eventBus = new EventBus();