thorish
Version:
This is a library of useful JS concepts and data structures for Node and the browser. It it, unashamedly, a dumping ground for code needed by [@samthor](https://twitter.com/samthor)'s projects.
128 lines (127 loc) • 5.66 kB
TypeScript
/**
* Channel is borrowed from Go.
*
* It doesn't have a notion of buffered; it's not for communication, just for scheduling.
* Channels have infinite buffer space, however, you can use {@link Channel.push} to wait for a pushed value to be consumed.
*
* It's similar to but subtly different from a {@link AsyncGenerator}.
* Whereas calling {@link AsyncGenerator.next} repeatedly creates a {@link Promise} of the next value, here, we instead wait for a value to be available before choosing to obtain it.
* In this way it lends itself to "pull" semantics; the consumer of one or many {@link Channel} instances can decide how to proceed.
*/
export type Channel<T> = {
/**
* Push this value into the {@link Channel}.
* Returns a {@link Promise} which resolves when it is consumed (optional).
*/
push(t: T): Promise<void>;
/**
* Closes this channel.
* No more values can be pushed, and the given value will be provided in perpetuity (i.e., will always be available).
*
* You must disambiguate T yourself to identify a closed channel.
*/
close(t: T): void;
/**
* Waits until a value from this channel is available.
*
* This is as {@link ReadChannel.wait}, but with a wider type.
*/
wait<V = Channel<T>>(value?: V): Promise<V>;
} & ReadChannel<T>;
export type ReadChannel<T> = {
/**
* Returns whether this channel is closed _and_ the close value has been provided via {@link ReadChannel.next}.
*
* This can be used to e.g., prematurely exit a loop or check what value was just retrieved.
*/
readonly closed: boolean;
/**
* Waits until a value from this channel is available.
*
* Repeated calls to `wait()` will delay a microtask before resolving.
* This allows a resolved task to safely consume a value synchronously after this resolves.
*
* For no other reason than convenience, this resolves with itself, unless you include another value as an argument.
* The assumption is you can then synchronously consume the next value with {@link ReadChannel#next}.
*/
wait<V = ReadChannel<T>>(value?: V): Promise<V>;
/**
* Returns whether {@link ReadChannel#next} has an available value.
*/
pending(): boolean;
/**
* Consumes a value from this {@link ReadChannel}, if possible.
* Otherwise, returns `undefined`.
*/
next(): T | undefined;
};
type MessageType<Q> = Q extends ReadChannel<infer X> ? X : never;
export type SelectRequest = {
[key: string | symbol]: ReadChannel<any> | undefined;
};
export type SelectResult<TChannels extends SelectRequest> = {
[TKey in keyof TChannels]: Readonly<{
key: TKey;
ch: NonNullable<TChannels[TKey]>;
m: MessageType<TChannels[TKey]>;
closed: boolean;
}>;
}[keyof TChannels];
export type ReadyResult<TChannels extends SelectRequest> = {
[TKey in keyof TChannels]: Readonly<{
key: TKey;
ch: NonNullable<TChannels[TKey]>;
closed: boolean;
}>;
}[keyof TChannels];
/**
* Waits for the first {@link Channel} that is ready based on key, reading that channel.
* Returns with the key for matching.
*
* For safety, throws if no valid channels are passed (zero channels or all `undefined`).
*
* This uses JS' default object ordering for what is "first" when many are ready: integers >= 0 in order, all others, symbols.
*/
export declare function select<TChannels extends SelectRequest>(o: TChannels): Promise<SelectResult<TChannels>>;
/**
* Waits for the first {@link Channel} that is ready based on key, reading that channel.
* Returns with the key for matching.
* Prefers the passed {@link AbortSignal}, which if aborted, returns `undefined`.
*
* Does not throw if missing channels, as the {@link AbortSignal} is an implied 'channel'.
*
* This uses JS' default object ordering for what is "first" when many are ready: integers >= 0 in order, all others, symbols.
*/
export declare function select<TChannels extends SelectRequest>(o: TChannels, signal: AbortSignal): Promise<SelectResult<TChannels> | undefined>;
export declare function select<TChannels extends SelectRequest>(o: TChannels): Promise<SelectResult<TChannels>>;
/**
* Selects the first {@link ReadChannel} that is ready, reading that channel, or `undefined` if none are ready.
* Returns with the key for matching.
*
* This uses JS' default object ordering for what is "first" when many are ready: integers >= 0 in order, all others, symbols.
*/
export declare function selectDefault<TChannels extends {
[key: string | symbol]: ReadChannel<any> | undefined;
}>(o: TChannels): SelectResult<TChannels> | undefined;
/**
* Selects the first {@link ReadChannel} that is ready, or `undefined` if none are ready.
* Returns with the key for matching.
*
* This uses JS' default object ordering for what is "first" when many are ready: integers >= 0 in order, all others, symbols.
*/
export declare function readyDefault<TChannels extends {
[key: string | symbol]: ReadChannel<any> | undefined;
}>(o: TChannels): ReadyResult<TChannels> | undefined;
/**
* Builds a new {@link Channel}.
*/
export declare function newChannel<T>(): Channel<T>;
/**
* Converts a {@link AbortSignal} into a channel which is closed when it aborts.
*/
export declare function channelForSignal<T = AbortSignal>(s: AbortSignal, v?: T): ReadChannel<T>;
/**
* Converts a {@link AsyncGenerator} to a channel which contains {@link IteratorResult}.
*/
export declare function channelForGenerator<T, TReturn>(g: AsyncGenerator<T, TReturn, void>): ReadChannel<IteratorResult<T, TReturn>>;
export {};