UNPKG

@akala/core

Version:
179 lines (162 loc) 6.39 kB
import type { AsyncEventBus, EventBus } from "./events/index.js"; import { Event, type EventKeys, type IEvent, type IEventSink, type EventListener } from "./events/shared.js"; export type ResolveHandler<T, TResult> = (value: T) => TResult | PromiseLike<TResult> export type RejectHandler<TResult> = (reason: unknown) => void | TResult | PromiseLike<TResult>; export { isPromiseLike } from './teardown-manager.js' /** * Converts a Promise to an Event emitter that fires when the Promise resolves * @template T - The type of the Promise resolution value * @param {PromiseLike<T>} promise - The Promise to convert * @returns {IEventSink<[T], void, unknown>} Event emitter that will emit the resolved value */ export function toEvent<T>(promise: PromiseLike<T>): IEventSink<[T], void, unknown> { const result = new Event<[T]>(Event.maxListeners, () => { }); promise.then(v => { try { result.emit(v); } finally { result[Symbol.dispose]() } }); return result; } /** * Converts an Event emitter to a Promise that resolves when the event fires * @template T - The type of the event payload * @param {IEventSink<[T], void, unknown>} event - The event emitter to convert * @returns {PromiseLike<T>} Promise that resolves with the first event payload */ export function fromEvent<T>(event: IEventSink<[T], void, unknown>): PromiseLike<T> { const result = new Deferred<T>(); event.addListener(value => result.resolve(value), { once: true }); return result; } /** * Converts an Event emitter to a Promise that resolves when the event fires * @template T - The type of the event payload * @param {IEventSink<[T], void, unknown>} event - The event emitter to convert * @returns {PromiseLike<T>} Promise that resolves with the first event payload */ export function fromEventBus<const TEvents extends { [key: PropertyKey]: IEvent<[T], void> }, T>(bus: EventBus<TEvents>, eventName: EventKeys<TEvents>): PromiseLike<T> { const result = new Deferred<T>(); bus.once(eventName, (value => result.resolve(value)) as EventListener<TEvents[keyof TEvents]>); return result; } /** * Converts an Event emitter to a Promise that resolves when the event fires * @template T - The type of the event payload * @param {IEventSink<[T], void, unknown>} event - The event emitter to convert * @returns {PromiseLike<T>} Promise that resolves with the first event payload */ export async function fromAsyncEventBus<const TEvents extends { [key: PropertyKey]: IEvent<[T], Promise<void> | void> }, T>(bus: AsyncEventBus<TEvents>, eventName: EventKeys<TEvents>): Promise<PromiseLike<T>> { const result = new Deferred<T>(); await bus.once(eventName, (value => result.resolve(value)) as EventListener<TEvents[keyof TEvents]>); return result; } /** * A Deferred Promise pattern implementation allowing external resolution control * @template T - The type of the resolved value * @template TError - The type of the rejection reason (defaults to Error) */ export class Deferred<T, TError = Error> implements PromiseLike<T> { private _resolve?: (value?: T | PromiseLike<T> | undefined) => void; private _reject?: (reason?: TError) => void; promise: Promise<T>; /** * Resolves the deferred Promise with a value * @param {T | PromiseLike<T> | undefined} _value - The resolution value * @throws {Error} If called before Promise initialization */ resolve(_value?: T | PromiseLike<T> | undefined): void { if (typeof (this._resolve) == 'undefined') throw new Error('Not Implemented'); this._resolve(_value); } /** * Rejects the deferred Promise with a reason * @param {TError} _reason - The rejection reason * @throws {Error} If called before Promise initialization */ reject(_reason?: TError): void { if (typeof (this._reject) == 'undefined') throw new Error('Not Implemented'); this._reject(_reason); } constructor() { let _resolve; let _reject; this.promise = new Promise<T>((resolve, reject) => { _resolve = resolve; _reject = reject; }); this._resolve = _resolve; this._reject = _reject; } public then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: TError) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2> { return this.promise.then(onfulfilled, onrejected); } public catch<TResult = never>(onrejected?: ((reason: TError) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult> { return this.promise.catch(onrejected); } public finally(onfinally?: (() => void) | undefined | null): Promise<T> { return this.promise.finally(onfinally); } public get [Symbol.toStringTag](): string { return this.promise[Symbol.toStringTag]; } } /** * Creates a Promise that resolves after a specified delay * @param {number} delay - Delay duration in milliseconds * @returns {Promise<void>} Promise that resolves after the delay */ export function delay(delay: number) { return new Promise((resolve) => { setTimeout(resolve, delay); }) } /** * Wraps a Promise with a timeout, rejecting if it doesn't resolve in time * @template T - The type of the original Promise resolution * @param {PromiseLike<T>} promise - The Promise to wrap * @param {number} timeoutInMs - Timeout duration in milliseconds * @returns {PromiseLike<T>} New Promise that either resolves with the original value or rejects with 'timeout' */ export function whenOrTimeout<T>(promise: PromiseLike<T>, timeoutInMs: number): PromiseLike<T> { return new Promise<T>((resolve, reject) => { const timeOut = setTimeout(function () { reject('timeout'); }, timeoutInMs); promise.then(function (data) { clearTimeout(timeOut); resolve(data); }, function (rejection) { clearTimeout(timeOut); reject(rejection); }); }) } /** * Enum representing the possible states of a Promise * @enum {number} */ export enum PromiseStatus { Pending = 0, Resolved = 1, Rejected = 2 }