first commit
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# toAsyncIterable
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`toAsyncIterable` adapts a `ReadableStream<T>` into a standard
|
||||
`AsyncIterable<T>`, letting consumers use ergonomic `for await ... of` loops
|
||||
to drain a stream rather than driving the WHATWG reader API by hand. While
|
||||
modern Node and recent browser engines expose `ReadableStream[Symbol.asyncIterator]`
|
||||
natively, the streams package ships its own implementation so that:
|
||||
|
||||
- behaviour is identical across runtimes regardless of native support;
|
||||
- abort semantics are pinned — aborts always surface as `StreamAbortedError`;
|
||||
- early-break cleanup is guaranteed (the underlying reader is cancelled, the
|
||||
lock is released, and abort listeners are removed) so upstream producers
|
||||
can promptly free resources.
|
||||
|
||||
## Features
|
||||
|
||||
- `for await ... of` support for any `ReadableStream<T>`.
|
||||
- Optional `AbortSignal` input — abort throws `StreamAbortedError`.
|
||||
- Cancels the underlying reader on early-break (via the iterator's
|
||||
`return()` hook).
|
||||
- Idempotent cleanup — releasing an already-released lock is swallowed.
|
||||
- Generic over `T`; works equally for `Uint8Array` byte streams and
|
||||
arbitrary object streams.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { toAsyncIterable } from "@suaveplan/streams/sinks/to-async-iterable/to-async-iterable";
|
||||
|
||||
for await (const chunk of toAsyncIterable(stream)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { toAsyncIterable } from "@suaveplan/streams/sinks/to-async-iterable/to-async-iterable";
|
||||
import { StreamAbortedError } from "@suaveplan/streams/errors/stream-error";
|
||||
|
||||
async function consumeWithDeadline<T>(
|
||||
stream: ReadableStream<T>,
|
||||
deadlineMs: number,
|
||||
onItem: (item: T) => Promise<void>
|
||||
): Promise<void> {
|
||||
const ac = new AbortController();
|
||||
const timer = setTimeout(() => ac.abort(new Error("deadline")), deadlineMs);
|
||||
try {
|
||||
for await (const item of toAsyncIterable(stream, ac.signal)) {
|
||||
await onItem(item);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof StreamAbortedError) {
|
||||
console.warn("hit deadline before stream completed");
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `toAsyncIterable<T>(stream, signal?): AsyncIterable<T>`
|
||||
|
||||
- **`stream: ReadableStream<T>`** — source stream to adapt.
|
||||
- **`signal?: AbortSignal`** — optional cancellation token. Aborting causes
|
||||
the active or next `next()` call to reject with `StreamAbortedError` and
|
||||
cancels the underlying reader.
|
||||
- **Returns** — an `AsyncIterable<T>` that yields chunks until the stream
|
||||
closes or aborts.
|
||||
- **Throws** — `StreamAbortedError` on abort; otherwise propagates upstream
|
||||
stream errors verbatim.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The iterator implements `return()` so `break`/`throw` inside the
|
||||
`for await` loop both trigger reader cancel + lock release. Without this,
|
||||
upstream producers would keep filling buffers for chunks no consumer will
|
||||
ever read.
|
||||
- Both pre-iteration abort and mid-`read()` abort paths produce
|
||||
`StreamAbortedError`. The mid-read path uses the same `aborted` flag plus
|
||||
a defensive re-check after `read()` resolves to handle the race where the
|
||||
signal fires after the read promise was already in flight.
|
||||
- Abort listeners are registered with `{ once: true }` and explicitly
|
||||
removed in cleanup — long-lived signals shared across many iterators do
|
||||
not accumulate listeners.
|
||||
Reference in New Issue
Block a user