@johngw/stream
Version:
Reactive programming tools using the WHATWG Streams API.
84 lines • 2.35 kB
JavaScript
import { without } from '@johngw/stream-common/Array';
/**
* The underlying source of a ReadableStream that can have chunks
* queued to from an external source.
*
* @group Sources
* @example
* Queuing items externally.
*
* ```
* const controller = new ControllableSource<number>()
* const stream = new ReadableStream(controller)
* controller.enqueue(1)
* controller.enqueue(2)
* controller.enqueue(3)
* controller.close()
* ```
*
* Registering pull subscribers externally.
*
* ```
* const controller = new ControllableStream<number>()
* let i = -1
* controller.onPull(() => ++i)
* ```
*/
export class ControllableSource {
#controller;
#pullListeners = [];
start(controller) {
this.#controller = controller;
}
async pull(controller) {
if (!this.#pullListeners.length)
return;
/*
At first glance you may wonder why we're not using
Promise.all. This is because we have no idea how long
each pullListener is going to take and we may want to
fill the desired size before all listeners have resolved.
Therefore we resolve when the 1st listener queues an item
so the stream can request more if it needs.
*/
return new Promise((resolve) => {
this.#pullListeners.forEach(async (pullListener) => {
try {
controller.enqueue(await pullListener());
}
catch (error) {
controller.error(error);
}
finally {
resolve();
}
});
});
}
get desiredSize() {
return this.#controller.desiredSize;
}
/**
* Register a pull subscriber.
*
* @remark
* When the stream is ready to pull it will pull from all
* subscribers until the desired size has been fulfilled.
*/
onPull(pullListener) {
this.#pullListeners.push(pullListener);
return () => {
this.#pullListeners = without(this.#pullListeners, pullListener);
};
}
error(error) {
this.#controller.error(error);
}
enqueue(chunk) {
this.#controller.enqueue(chunk);
}
close() {
this.#controller.close();
}
}
//# sourceMappingURL=ControllableSource.js.map