3.4 KiB
3.4 KiB
toAsyncIterable
(no submodules)
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 ... ofsupport for anyReadableStream<T>.- Optional
AbortSignalinput — abort throwsStreamAbortedError. - 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 forUint8Arraybyte streams and arbitrary object streams.
Usage: Basic Example
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
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 nextnext()call to reject withStreamAbortedErrorand cancels the underlying reader.- Returns — an
AsyncIterable<T>that yields chunks until the stream closes or aborts. - Throws —
StreamAbortedErroron abort; otherwise propagates upstream stream errors verbatim.
Implementation Notes
- The iterator implements
return()sobreak/throwinside thefor awaitloop 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 produceStreamAbortedError. The mid-read path uses the sameabortedflag plus a defensive re-check afterread()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.