//@ts-check /// /** * Returns a native browser event target that is properly typed. * Three major differences with a classical event target: * * 1. The emitter's methods are bound and can be passed to other objects * 2. The emitter has an `abort` property and a `signal` property that can be * used to abort all listeners (you have to explicitely pass it though, it's * not automatic) * 3. `dispatchEvent` has a different signature `(type, event)` rather than just * `event`. This is because there is no way to enforce a string & details * tuple on a CustomEvent using Typescript or JSDocs. * @template {CustomEventMap} EvtMap * @returns {CustomEventEmitter} */ export const makeEventEmitter = () => { let abortController = new AbortController(); const eventEmitter = new EventTarget(); const addEventListener = eventEmitter.addEventListener.bind(eventEmitter); const removeEventListener = eventEmitter.removeEventListener.bind(eventEmitter); /** * Dispatches a custom event to all listeners of that event. * @type {CustomEventEmitter["dispatchEvent"]} */ const dispatchEvent = (type, detail) => { const event = new CustomEvent(type, { detail }); eventEmitter.dispatchEvent(event); }; /** * Aborts any eventListener, fetch, or other process that received the signal. * resets the abort controller and signal (they are new instances) * @param {any} reason */ const abort = (reason) => { abortController.abort(reason); abortController = new AbortController(); }; return { dispatchEvent, addEventListener, removeEventListener, abort, get signal() { return abortController.signal; }, }; }; export default makeEventEmitter