it-queueless-pushable
Version:
A pushable queue that waits until a value is consumed before accepting another
125 lines • 3.82 kB
JavaScript
/**
* @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