/** * Promise that resolves after timeout, optionally with a value to resolve with. * * @example * // Play hide and seek, count to 100 (seconds) * // Both methods are identical * wait(100e3).then(() => "Ready or not, here I come!").then(seek) * wait(100e3, "Ready or not, here I come!").then(seek) * @since 2020-06-23 */ export function wait(timeout: number): Promise export function wait(timeout: number, resolveWith: T): Promise export function wait(timeout: number, resolveWith?: T): Promise { return new Promise(resolve => setTimeout(() => resolve(resolveWith), timeout)) } /** * Helper class to handle events and turn them into async iterables * * ```ts * const stream = new EventStream() * * document.body.addEventListener('click', ev => stream.emit(ev)) * wait(10e3).then(() => stream.end()) * * for await (const event of stream) { * alert("Clicked!") * } * ``` * @since 2021-08-07 */ export class EventStream implements AsyncIterable { #done: boolean #events: T[] #resolve: () => void #promise!: Promise constructor() { this.#done = false this.#events = [] this.#resolve = () => { } this.#defer() } #defer() { this.#promise = new Promise(r => this.#resolve = r) } async*[Symbol.asyncIterator](): AsyncGenerator, void, undefined> { while (!this.#done) { await this.#promise yield* this.#events this.#events = [] } } /** * Dispatches an event. For-await-of listeners of this class instance will recieve this event. * Note that once an event is emitted, it cannot be cancelled. * @since 2021-08-07 */ emit(event: T) { this.#events.push(event) this.#resolve() this.#defer() } /** * Waits for a specific event to be emitted according to a predicate. * * @example * const stream = new EventStream() * // Some events happen... * const click = stream.once(ev => ev.type === 'click') * @since 2021-12-22 */ once(predicate: (value: T) => boolean): Promise | undefined> { return once(this, predicate) } /** * Stops the iterator. This terminates for-await-of loops listening for events, * and any newly dispatched events will not be sent to the iterator. * @since 2021-08-07 */ end() { this.#done = true this.#resolve() } /** * Iterate over the event stream, calling a callback for each event. * @author MindfulMinun * @since 2024-06-02 */ each(callback: (value: T) => void): Promise { return each(this, callback) } } /** * Wait for any async iterable to yield a specific value according to a predicate. * * @example * const stream = new EventStream() * // Some events happen... * const click = once(stream, ev => ev.type === 'click') * @since 2021-12-22 */ export async function once(source: AsyncIterable, predicate: (value: T) => boolean): Promise { for await (const value of source) { if (predicate(value)) return value } } /** * Iterate over an async iterable, calling a callback for each value. * * @author MindfulMinun * @since 2024-06-02 */ export async function each(source: AsyncIterable, callback: (value: T) => void) { for await (const value of source) { callback(value) } } /** * A "destructured" Promise, useful for waiting for callbacks. * @example * // When awaited, this function returns a promise that resolves to * // an *OPEN* WebSocket * () => { * const sock = new WebSocket('wss://wss.example.com') * const [p, res, rej] = pinkyPromise() * sock.onerror = err => rej(err) * sock.onopen = () => res(sock) * return p * } * * @author MindfulMinun * @since 2022-06-05 */ export function pinkyPromise(): [Promise, () => void, (reason?: unknown) => void] export function pinkyPromise(): [Promise, (value: T) => void, (reason?: unknown) => void] export function pinkyPromise(): [ Promise, (value?: T | PromiseLike) => void, (reason?: unknown) => void ] { let resolve: (value?: T | PromiseLike) => void let reject: (error: unknown) => void // This works because callbacks to the Promise constructor are called synchronously. const p = new Promise((yay, nay) => { resolve = value => yay(value) reject = reason => nay(reason) }) // HACK: This works because callbacks to the Promise constructor are called synchronously. // In other words, they're immediately invoked, so they're available immediately after // the assignment to `p` return [p, resolve!, reject!] }