55 lines
1.8 KiB
JavaScript
55 lines
1.8 KiB
JavaScript
//@ts-check
|
|
/// <reference path="makeEventEmitter.d.ts"/>
|
|
|
|
/**
|
|
* 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<EvtMap>}
|
|
*/
|
|
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<EvtMap>["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 |