@johngw/stream
Version:
Reactive programming tools using the WHATWG Streams API.
67 lines (60 loc) • 1.55 kB
text/typescript
/**
* Options for {@link toIterator}.
*
* @group Sinks
*/
export interface ToIteratorOptions {
signal: AbortSignal
}
/**
* Turns a `ReadableStream` in to an `AsyncIterator`.
*
* @group Sinks
* @see {@link toIterable:function}
* @example
* ```
* const iterator = toIterator(stream)
* while (true) {
* const result = await iterator.next()
* if (result.done) break
* doSomething(result.value)
* }
* ```
*/
export function toIterator<T>(
stream: ReadableStream<T>,
options?: ToIteratorOptions
): AsyncIterator<T> {
return new StreamIterator(stream, options?.signal)
}
class StreamIterator<T> implements AsyncIterator<T> {
#reader: ReadableStreamDefaultReader<T>
#signal?: AbortSignal
constructor(stream: ReadableStream<T>, signal?: AbortSignal) {
this.#reader = stream.getReader()
this.#signal = signal
if (signal?.aborted) this.throw(signal.reason)
signal?.addEventListener('abort', () => this.throw(signal.reason))
}
next() {
return this.#signal?.aborted
? Promise.reject(this.#signal.reason)
: (this.#reader.read() as Promise<IteratorResult<T>>)
}
async return(value: T): Promise<IteratorResult<T>> {
try {
await this.#reader.cancel()
} catch (error) {
// potentially already closed
}
return { done: true, value }
}
async throw(reason?: unknown): Promise<IteratorResult<T>> {
try {
await this.#reader.cancel(reason)
} catch (error) {
// potentially already closed
}
return { done: true, value: undefined }
}
}