Spaces:
Running
Running
File size: 4,517 Bytes
9bd422a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | /**
* EventBus - Hα» Thα»ng Sα»± Kiα»n Trung TΓ’m
* Cung cαΊ₯p giao tiαΊΏp dα»±a trΓͺn sα»± kiα»n giα»―a cΓ‘c thΓ nh phαΊ§n
* Requirements: 2.2, 11.4
*/
const EventBus = (function () {
// βββ Private State ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/** @type {Map<string, Set<Function>>} */
const _listeners = new Map();
// βββ Private Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/**
* Validate event name.
* @param {string} eventName
*/
function _validateEvent(eventName) {
if (!eventName || typeof eventName !== 'string') {
throw new TypeError('[EventBus] Event name must be a non-empty string');
}
}
// βββ Public API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
return {
/**
* Subscribe to an event.
* @param {string} eventName - Event name to listen for
* @param {Function} callback - Handler called with event data
* @returns {Function} Unsubscribe function
*/
on(eventName, callback) {
_validateEvent(eventName);
if (typeof callback !== 'function') {
throw new TypeError('[EventBus] Callback must be a function');
}
if (!_listeners.has(eventName)) {
_listeners.set(eventName, new Set());
}
_listeners.get(eventName).add(callback);
// Return unsubscribe function
return function off() {
const set = _listeners.get(eventName);
if (set) {
set.delete(callback);
if (set.size === 0) {
_listeners.delete(eventName);
}
}
};
},
/**
* Unsubscribe a specific callback from an event.
* @param {string} eventName
* @param {Function} callback
*/
off(eventName, callback) {
_validateEvent(eventName);
const set = _listeners.get(eventName);
if (set) {
set.delete(callback);
if (set.size === 0) {
_listeners.delete(eventName);
}
}
},
/**
* Emit an event with optional data.
* @param {string} eventName
* @param {*} [data]
*/
emit(eventName, data) {
_validateEvent(eventName);
const set = _listeners.get(eventName);
if (!set || set.size === 0) return;
// Iterate over a copy to allow handlers to call off() safely
const handlers = Array.from(set);
for (const handler of handlers) {
try {
handler(data);
} catch (err) {
console.error(`[EventBus] Error in handler for "${eventName}":`, err);
}
}
},
/**
* Subscribe to an event for a single invocation, then auto-unsubscribe.
* @param {string} eventName
* @param {Function} callback
* @returns {Function} Unsubscribe function
*/
once(eventName, callback) {
const unsubscribe = this.on(eventName, (data) => {
unsubscribe();
callback(data);
});
return unsubscribe;
},
/**
* Remove all listeners for a specific event, or all events if no name given.
* @param {string} [eventName]
*/
clear(eventName) {
if (eventName) {
_listeners.delete(eventName);
} else {
_listeners.clear();
}
},
/**
* Get the number of listeners for an event (useful for debugging/testing).
* @param {string} eventName
* @returns {number}
*/
listenerCount(eventName) {
const set = _listeners.get(eventName);
return set ? set.size : 0;
}
};
})();
// Export as global for browser usage
window.EventBus = EventBus;
|