Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/cdellacqua/channel.js
A simple yet powerful abstraction that enables communication between asynchronous tasks.
https://github.com/cdellacqua/channel.js
async data-structures
Last synced: about 2 months ago
JSON representation
A simple yet powerful abstraction that enables communication between asynchronous tasks.
- Host: GitHub
- URL: https://github.com/cdellacqua/channel.js
- Owner: cdellacqua
- License: mit
- Created: 2022-07-02T15:49:27.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-01-14T20:07:50.000Z (about 1 year ago)
- Last Synced: 2024-11-01T09:17:07.322Z (3 months ago)
- Topics: async, data-structures
- Language: TypeScript
- Homepage:
- Size: 411 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# reactive-channel
A simple yet powerful abstraction that enables communication between asynchronous tasks.
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 to create
a simplex communication: one task transmits 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.[NPM Package](https://www.npmjs.com/package/reactive-channel)
`npm install reactive-channel`
[Documentation](./docs/README.md)
## Highlights
`Channel` provides the following properties:
- `tx`, an object that exposes methods and properties to interact and query the transmitting end of the channel;
- `rx`, an object that exposes methods and properties to interact and query the receiving end of the channel;
- `buffer`, a readonly queue that represents the internal buffer containing data waiting to be consumed by the receiving end.The `tx` object is a `ChannelTx`, which in turn exposes the following:
- `capacity`, the total capacity of the channel, configured at creation time;
- `send(...)`, to send data to the receiving end. If the receiving end is
not waiting for data, this method will enqueue the message in the channel
buffer;
- `sendWait(...)`, similar to `send(...)`, but let the caller wait for
the receiving end to consume the data;
- `close()`, to close the channel. This will also reject all pending receiving Promises;
- `closed$`, a boolean store that can be used to check
if the channel has been closed, i.e. the `close()` method has been called by either the receiving or transmission end of the channel;
- `canWrite$`, a boolean store that can be used to check
if the channel is capable of receiving data, that is its buffer is not full and the channel is not closed;
- `availableOutboxSlots$`, a store that can be used to check how much space is available in the transmission buffer.The `rx` object is a `ChannelRx`, which in turn exposes the following:
- `capacity`, the total capacity of the channel, configured at creation time;
- `recv(...)`, to receive data. If the transmission buffer contains some data
the returned promise resolves immediately, otherwise it will resolve once
the transmitting end calls `send(...)` or `sendWait(...)`;
- `iter()/[Symbol.asyncIterator]()`, to consume the channel buffer using an async iterator;
- `close()`, to close the channel. This will also reject all pending receiving Promises;
- `closed$`, a store store that can be used to check
if the channel has been closed, i.e. the `close()` method has been called by either the receiving or transmission end of the channel;
- `canRead$`, a boolean store that can be used to check
if the channel can be consumed, that is its buffer is not empty and the channel is not closed;
- `filledInboxSlots$`, a store that can be used to check how much space is filled in the transmission buffer;
- `pendingRecvPromises$`, a store that can be used to check how many `recv` calls are pending.This library provides a function called `makeChannel` to create `Channel` objects.
### Examples
Basics:
```ts
import {makeChannel} from 'reactive-channel';// Create a channel that can be used to pass strings.
const {rx, tx} = makeChannel();
rx.recv().then((message) => console.log(message));
// ...
tx.send('hello!'); // this will resolve the above promise, printing "hello!"
```Reactivity:
```ts
import {makeChannel} from 'reactive-channel';// Create a channel that can be used to pass strings.
const {rx, tx} = makeChannel();
tx.availableOutboxSlots$
// this will immediately print 1024, which is the default channel capacity.
.subscribe((n) => console.log(`Available slots: ${n}`));// this will trigger the above subscription, printing 1023,
// because there is no pending `recv` that can consume the data.
tx.send('hello!');
// this will also trigger the subscription, printing 1024 again,
// because `recv()` will consume the channel buffer, emptying it.
rx.recv();
```Simple job queue:
```ts
import {makeChannel} from 'reactive-channel';// Create a channel that can be used to pass strings.
const {rx, tx} = makeChannel<{email: string; content: string}>();async function processEmailQueue() {
while (!rx.closed$.content()) {
try {
const {email, content} = await rx.recv();
console.log(`Now sending an email to ${email}`);
// ... your favorite email dispatcher here.
} catch (err) {
console.error('unable to send because of the following error:', err);
}
}
}// Somewhere in the startup of your application.
processEmailQueue();// Somewhere where an email should be sent, e.g. when a user subscribes to your newsletter.
tx.send({
email: user.email,
content: 'Subscription activated, you will receive our best deals and coupons!',
});
```Abort a blocking `recv(...)`
You can abort a `recv` using a simple timeout:
```ts
import {makeChannel} from 'reactive-channel';// Create a channel that can be used to pass strings.
const {rx, tx} = makeChannel<{email: string; content: string}>();
// You can abort a `recv` using a simple timeout:
try {
const {email, content} = await rx.recv({signal: AbortSignal.timeout(5000)});
} catch (err) {
if (err instanceof DOMException && err.name === 'TimeoutError') {
console.warn('No data to consume, timeout expired');
}
// ...
}
```Or you can abort a `recv` using some custom logic, e.g. when the user clicks a button:
```ts
import {makeChannel} from 'reactive-channel';// Create a channel that can be used to pass strings.
const {rx, tx} = makeChannel<{email: string; content: string}>();const abortController = new AbortController();
// Assuming you have a `abortButton` variable
abortButton.addEventListener(
'click',
() => abortController.abort(new Error('Action cancelled by the user'))
);// You can abort a `recv` using an abort signal:
try {
const {email, content} = await rx.recv({signal: abortController.signal});
} catch (err) {
// ...
}
```