1
0

NanoEmitter.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /**
  2. * @module lib/NanoEmitter
  3. * This module contains the NanoEmitter class, which is a tiny event emitter powered by [nanoevents](https://www.npmjs.com/package/nanoevents) - [see the documentation for more info](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#nanoemitter)
  4. */
  5. import { createNanoEvents, type DefaultEvents, type Emitter, type EventsMap, type Unsubscribe } from "nanoevents";
  6. export interface NanoEmitterOptions {
  7. /** If set to true, allows emitting events through the public method emit() */
  8. publicEmit: boolean;
  9. }
  10. /**
  11. * Class that can be extended or instantiated by itself to create a lightweight event emitter with helper methods and a strongly typed event map.
  12. * If extended from, you can use `this.events.emit()` to emit events, even if the `emit()` method doesn't work because `publicEmit` is not set to true in the constructor.
  13. */
  14. export class NanoEmitter<TEvtMap extends EventsMap = DefaultEvents> {
  15. protected readonly events: Emitter<TEvtMap> = createNanoEvents<TEvtMap>();
  16. protected eventUnsubscribes: Unsubscribe[] = [];
  17. protected emitterOptions: NanoEmitterOptions;
  18. /** Creates a new instance of NanoEmitter - a lightweight event emitter with helper methods and a strongly typed event map */
  19. constructor(options: Partial<NanoEmitterOptions> = {}) {
  20. this.emitterOptions = {
  21. publicEmit: false,
  22. ...options,
  23. };
  24. }
  25. /**
  26. * Subscribes to an event and calls the callback when it's emitted.
  27. * @param event The event to subscribe to. Use `as "_"` in case your event names aren't thoroughly typed (like when using a template literal, e.g. \`event-${val}\` as "_")
  28. * @returns Returns a function that can be called to unsubscribe the event listener
  29. * @example ```ts
  30. * const emitter = new NanoEmitter<{
  31. * foo: (bar: string) => void;
  32. * }>({
  33. * publicEmit: true,
  34. * });
  35. *
  36. * let i = 0;
  37. * const unsub = emitter.on("foo", (bar) => {
  38. * // unsubscribe after 10 events:
  39. * if(++i === 10) unsub();
  40. * console.log(bar);
  41. * });
  42. *
  43. * emitter.emit("foo", "bar");
  44. * ```
  45. */
  46. public on<TKey extends keyof TEvtMap>(event: TKey | "_", cb: TEvtMap[TKey]): () => void {
  47. // eslint-disable-next-line prefer-const
  48. let unsub: Unsubscribe | undefined;
  49. const unsubProxy = (): void => {
  50. if(!unsub)
  51. return;
  52. unsub();
  53. this.eventUnsubscribes = this.eventUnsubscribes.filter(u => u !== unsub);
  54. };
  55. unsub = this.events.on(event, cb);
  56. this.eventUnsubscribes.push(unsub);
  57. return unsubProxy;
  58. }
  59. /**
  60. * Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
  61. * @param event The event to subscribe to. Use `as "_"` in case your event names aren't thoroughly typed (like when using a template literal, e.g. \`event-${val}\` as "_")
  62. * @param cb The callback to call when the event is emitted - if provided or not, the returned Promise will resolve with the event arguments
  63. * @returns Returns a Promise that resolves with the event arguments when the event is emitted
  64. * @example ```ts
  65. * const emitter = new NanoEmitter<{
  66. * foo: (bar: string) => void;
  67. * }>();
  68. *
  69. * // Promise syntax:
  70. * const [bar] = await emitter.once("foo");
  71. * console.log(bar);
  72. *
  73. * // Callback syntax:
  74. * emitter.once("foo", (bar) => console.log(bar));
  75. * ```
  76. */
  77. public once<TKey extends keyof TEvtMap>(event: TKey | "_", cb?: TEvtMap[TKey]): Promise<Parameters<TEvtMap[TKey]>> {
  78. return new Promise((resolve) => {
  79. // eslint-disable-next-line prefer-const
  80. let unsub: Unsubscribe | undefined;
  81. const onceProxy = ((...args: Parameters<TEvtMap[TKey]>) => {
  82. cb?.(...args);
  83. unsub?.();
  84. resolve(args);
  85. }) as TEvtMap[TKey];
  86. unsub = this.events.on(event, onceProxy);
  87. this.eventUnsubscribes.push(unsub);
  88. });
  89. }
  90. /**
  91. * Emits an event on this instance.
  92. * ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
  93. * @param event The event to emit
  94. * @param args The arguments to pass to the event listeners
  95. * @returns Returns true if `publicEmit` is true and the event was emitted successfully
  96. */
  97. public emit<TKey extends keyof TEvtMap>(event: TKey, ...args: Parameters<TEvtMap[TKey]>): boolean {
  98. if(this.emitterOptions.publicEmit) {
  99. this.events.emit(event, ...args);
  100. return true;
  101. }
  102. return false;
  103. }
  104. /** Unsubscribes all event listeners from this instance */
  105. public unsubscribeAll(): void {
  106. for(const unsub of this.eventUnsubscribes)
  107. unsub();
  108. this.eventUnsubscribes = [];
  109. }
  110. }