UNPKG

reactive-channel

Version:

A simple yet powerful abstraction that enables communication between asynchronous tasks.

198 lines (197 loc) 7.96 kB
import { ReadonlyCircularQueue } from 'reactive-circular-queue'; import { ReadonlyStore } from 'universal-stores'; export type { ReadonlyCircularQueue } from 'reactive-circular-queue'; export { NotEnoughAvailableSlotsQueueError, NotEnoughFilledSlotsQueueError, } from 'reactive-circular-queue'; export type { Unsubscribe, Subscriber, ReadonlyStore } from 'universal-stores'; /** * Error that occurs when the channel buffer has been filled up, and thus it cannot * accept any more `send` calls. */ export declare class ChannelFullError extends Error { constructor(); } /** * Error that occurs trying to send or receive data from * a closed channel. */ export declare class ChannelClosedError extends Error { constructor(); } /** * Error that occurs when calling `recv` * and there are already too many enqueued similar requests. */ export declare class ChannelTooManyPendingRecvError extends Error { constructor(); } /** * Transmission end of a channel. */ export type ChannelTx<T> = { /** * Push data into the channel. * This operation enqueues the passed value in the transmission queue if there * is no pending `recv`. * @param v the data to send. * @throws {ChannelClosedError} if the channel is closed. * @throws {ChannelFullError} if the channel is transmission queue is full. */ send(v: T): void; /** * Push data into the channel and waits for it to be consumed by the receiving end. * This operation enqueues the passed value in the transmission queue if there * is no pending `recv`, but removes it if the operation is aborted by an abort * signal. * @param v the data to send. * @param options.signal (optional) an abort signal to stop the pending promise. * If this signal emits before `sendWait` can resolve, the enqueued value will be removed * and the emitted value will be "thrown" (as in `throw ...;`) to the caller * of `sendWait`. * @throws {ChannelClosedError} if the channel is closed. * @throws {ChannelFullError} if the channel is transmission queue is full. * @throws {unknown} if `signal` triggers before `sendWait` can resolve. */ sendWait(v: T, options?: { signal?: AbortSignal; }): Promise<void>; /** * A store that contains true if the transmission buffer is not full and the channel is not closed. */ canWrite$: ReadonlyStore<boolean>; /** * A store that contains the number of available slots (from 0 to the channel capacity) in the output buffer or 0 if the channel is closed. */ availableOutboxSlots$: ReadonlyStore<number>; /** * Return the total size (number of slots) of the channel buffer. */ get capacity(): number; /** * Close the channel, stopping all pending send/recv requests. */ close(): void; /** * A store that contains true if the channel is closed. */ closed$: ReadonlyStore<boolean>; }; /** * Receiving end of a channel. */ export type ChannelRx<T> = { /** * A store that contains true if there is some data ready to be consumed, the channel is not closed and there are not too many pending `recv` requests. */ canRead$: ReadonlyStore<boolean>; /** * A store that contains the number of filled slots (from 0 to the channel capacity) in the input buffer or 0 if the channel is closed. */ filledInboxSlots$: ReadonlyStore<number>; /** * Return the total size (number of slots) of the channel buffer. */ get capacity(): number; /** * Consume data from the channel buffer. * If there is no data in the channel, this method will block the caller * until it's available. * @param options.signal (optional) an abort signal to stop the pending promise. * If this signal triggers before `recv` can resolve, the channel buffer won't be * consumed and the abort reason value will be "thrown" (as in `throw ...;`) to the caller * of `recv`. * @throws {ChannelClosedError} if the channel is closed. * @throws {unknown} if `.abort(...)` is called before `recv` is able to consume the channel buffer. */ recv(options?: { signal?: AbortSignal; }): Promise<T>; /** * Return an async iterator that consumes the channel buffer * If the channel buffer is already empty the iterator will not emit any value. */ iter(): AsyncIterator<T>; /** * Return an async iterator that consumes the channel buffer * If the channel buffer is already empty the iterator will not emit any value. */ [Symbol.asyncIterator](): AsyncIterator<T>; /** * Close the channel, stopping all pending send/recv requests. */ close(): void; /** * A store that contains true if the channel is closed. */ closed$: ReadonlyStore<boolean>; /** * A store that contains the number of currently waiting `recv` promises. */ pendingRecvPromises$: ReadonlyStore<number>; }; /** * A Channel is an abstraction that enables * communication between asynchronous tasks. * A channel exposes two objects: `tx` and `rx`, * which respectively provide methods to transmit * and receive data. * * Channels can be used and combined in a multitude of * ways. The simplest way to use a channel is by creating * a simplex communication: one task transmit data, another consumes it. * A full-duplex communication can be achieved by creating two channels * and exchanging the `rx` and `tx` objects between two tasks. * * It's also possible to create a Multiple Producers Single Consumer (mpsc) scenario * by sharing a single channel among several tasks. */ export type Channel<T> = { /** * Transmission end of the channel. */ tx: ChannelTx<T>; /** * Receiving end of the channel. */ rx: ChannelRx<T>; /** * Return the internal buffer in readonly mode. */ get buffer(): ReadonlyCircularQueue<T>; }; export type MakeChannelParams = { /** (optional, defaults to 1024) The maximum number of items that the channel can buffer while waiting data to be consumed. */ capacity?: number; /** (optional, defaults to 1024) The maximum number of pending `recv`. If this limit is reached, `recv` will immediately reject with {@link ChannelTooManyPendingRecvError}. */ maxConcurrentPendingRecv?: number; }; /** * Create a Channel. * * A Channel is an abstraction that enables * communication between asynchronous tasks. * A channel exposes two objects: `tx` and `rx`, * which respectively provide methods to transmit * and receive data. * * Channels can be used and combined in a multitude of * ways. The simplest way to use a channel is by creating * a simplex communication: one task transmit data, another consumes it. * A full-duplex communication can be achieved by creating two channels * and exchanging the `rx` and `tx` objects between two tasks. * * It's also possible to create a Multiple Producers Single Consumer (mpsc) scenario * by sharing a single channel among several tasks. * * Example: * ```ts * const {tx, rx} = makeChannel<number>(); * rx.recv().then((n) => console.log('Here it is: ' + n)); // doesn't print anything, the channel is currently empty. * tx.send(1); // resolves the above promise, causing it to print 'Here it is: 1' * ``` * * @param params (optional) configuration parameters for this channel (e.g maximum capacity). * @param params.capacity (optional, defaults to 1024) The maximum number of items that the channel can buffer while waiting data to be consumed. * @param params.maxConcurrentPendingRecv (optional, defaults to 1024) The maximum number of pending `recv`. If this limit is reached, `recv` will immediately reject with {@link ChannelTooManyPendingRecvError}. * @returns a {@link Channel} */ export declare function makeChannel<T>(params?: MakeChannelParams): Channel<T>;