UNPKG

it-queueless-pushable

Version:

A pushable queue that waits until a value is consumed before accepting another

125 lines 3.82 kB
/** * @packageDocumentation * * A pushable async generator that waits until the current value is consumed * before allowing a new value to be pushed. * * Useful for when you don't want to keep memory usage under control and/or * allow a downstream consumer to dictate how fast data flows through a pipe, * but you want to be able to apply a transform to that data. * * @example * * ```typescript * import { queuelessPushable } from 'it-queueless-pushable' * * const pushable = queuelessPushable<string>() * * // run asynchronously * Promise.resolve().then(async () => { * // push a value - the returned promise will not resolve until the value is * // read from the pushable * await pushable.push('hello') * }) * * // read a value * const result = await pushable.next() * console.info(result) // { done: false, value: 'hello' } * ``` */ import deferred, {} from 'p-defer'; import { raceSignal } from 'race-signal'; class QueuelessPushable { readNext; haveNext; ended; nextResult; error; constructor() { this.ended = false; this.readNext = deferred(); this.haveNext = deferred(); } [Symbol.asyncIterator]() { return this; } async next() { if (this.nextResult == null) { // wait for the supplier to push a value await this.haveNext.promise; } if (this.nextResult == null) { throw new Error('HaveNext promise resolved but nextResult was undefined'); } const nextResult = this.nextResult; this.nextResult = undefined; // signal to the supplier that we read the value this.readNext.resolve(); this.readNext = deferred(); return nextResult; } async throw(err) { this.ended = true; this.error = err; if (err != null) { // this can cause unhandled promise rejections if nothing is awaiting the // next value so attach a dummy catch listener to the promise this.haveNext.promise.catch(() => { }); this.haveNext.reject(err); } const result = { done: true, value: undefined }; return result; } async return() { const result = { done: true, value: undefined }; this.ended = true; this.nextResult = result; // let the consumer know we have a new value this.haveNext.resolve(); return result; } async push(value, options) { await this._push(value, options); } async end(err, options) { if (err != null) { await this.throw(err); } else { // abortable return await this._push(undefined, options); } } async _push(value, options) { if (value != null && this.ended) { throw this.error ?? new Error('Cannot push value onto an ended pushable'); } // wait for all values to be read while (this.nextResult != null) { await this.readNext.promise; } if (value != null) { this.nextResult = { done: false, value }; } else { this.ended = true; this.nextResult = { done: true, value: undefined }; } // let the consumer know we have a new value this.haveNext.resolve(); this.haveNext = deferred(); // wait for the consumer to have finished processing the value and requested // the next one or for the passed signal to abort the waiting await raceSignal(this.readNext.promise, options?.signal, options); } } export function queuelessPushable() { return new QueuelessPushable(); } //# sourceMappingURL=index.js.map