UNPKG

@hirosystems/api-toolkit

Version:
93 lines 3.37 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.onceWhen = onceWhen; const node_events_1 = require("node:events"); // This is a workaround for Node.js versions that do not support Symbol.dispose const DisposeSymbol = Symbol.dispose ?? Symbol.for('nodejs.dispose'); /** * Creates a Promise that resolves when the specified `eventName` is emitted by the `EventEmitter` * and the provided predicate returns `true` for the emitted arguments. * * Similar to [`events.once`](https://nodejs.org/api/events.html#eventsonceemitter-name-options), * but includes support for a predicate function to filter events. Only events for which * the predicate returns `true` will cause the Promise to resolve. * * The resolved value is an array of the arguments emitted with the event. * * Supports typed `EventEmitter`s and optional cancellation via `AbortSignal`. * * @example * ```ts * import { EventEmitter } from 'node:events'; * * const emitter = new EventEmitter<{ * myEvent: [id: number, msg: string]; * }>(); * * setTimeout(() => { * for (let i = 0; i <= 5; i++) { * emitter.emit('myEvent', i, `Message ${i}`); * } * }, 100); * * const [id, msg] = await onceWhen(emitter, 'myEvent', (id, msg) => id === 3); * * // outputs: "Received event with id: 3, message: Message 3" * console.log(`Received event with id: ${id}, message: ${msg}`); * ``` * * @example * ```ts * import { EventEmitter } from 'node:events'; * * const emitter = new EventEmitter<{ myEvent: [id: number, msg: string] }>(); * * const signal = AbortSignal.timeout(10); * * setTimeout(() => emitter.emit('myEvent', 1, 'Hello'), 1000); * * const whenPromise = onceWhen(emitter, 'myEvent', id => id === 1, { signal }); * * // This rejects because the signal is aborted before the event is emitted * await expect(whenPromise).rejects.toThrow(signal.reason); * ``` */ function onceWhen(emitter, eventName, predicate, options) { return new Promise((resolve, reject) => { // Immediate abort check if (options?.signal?.aborted) { reject(options.signal.reason ?? new Error('Aborted')); return; } // Cleanup helper: remove both the event listener and the abort listener const cleanup = () => { // eslint-disable-next-line @typescript-eslint/no-use-before-define emitter.off(eventName, listener); // eslint-disable-next-line @typescript-eslint/no-use-before-define disposable?.[DisposeSymbol](); }; // Abort handler const onAbort = () => { cleanup(); reject(options?.signal?.reason ?? new Error('Aborted')); }; // Our event listener that checks the predicate const listener = (...args) => { try { if (predicate(...args)) { cleanup(); resolve(args); } } catch (err) { cleanup(); reject(err); return; } }; // Install the AbortSignal listener via Node’s helper const disposable = options?.signal ? (0, node_events_1.addAbortListener)(options.signal, onAbort) : undefined; emitter.on(eventName, listener); }); } //# sourceMappingURL=events.js.map