reactive-channel
Version:
A simple yet powerful abstraction that enables communication between asynchronous tasks.
198 lines (197 loc) • 7.96 kB
TypeScript
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>;