UNPKG

@johngw/stream

Version:

Reactive programming tools using the WHATWG Streams API.

94 lines (80 loc) 2.34 kB
import { ReadableStreamsChunks } from '@johngw/stream-common/Stream' import { empty } from '@johngw/stream-common/Symbol' import { ControllableSource } from '@johngw/stream/sources/ControllableSource' import { SourceComposite } from '@johngw/stream/sources/SourceComposite' /** * Combines the source Observable with other Observables to create an Observable * whose values are calculated from the latest values of each, only when the source emits. * * @group Transformers * @example * ``` * --a-----b-------c---d---e-- * * withLatestFrom( * -----1----2-3-4------------ * ) * * --------b1------c4--d4--e4- * ``` */ export function withLatestFrom<T, RSs extends ReadableStream<unknown>[]>( ...inputs: RSs ): ReadableWritablePair<[T, ...ReadableStreamsChunks<RSs>], T> { const abortController = new AbortController() const isFilled = (arr: unknown[]): arr is ReadableStreamsChunks<RSs> => arr.every((value) => value !== empty) const inputValues: unknown[] = inputs.map(() => empty) inputs.forEach((input, index) => input .pipeTo( new WritableStream({ abort(reason) { abortController.abort(reason) }, write(chunk) { inputValues[index] = chunk }, }), { signal: abortController.signal } ) .catch(() => { // errors are handled in the stream object }) ) const controller = new ControllableSource< [T, ...ReadableStreamsChunks<RSs>] >() const readable = new ReadableStream<[T, ...ReadableStreamsChunks<RSs>]>( new SourceComposite([ controller, { start, cancel(reason) { abortController.abort(reason) }, }, ]) ) const writable = new WritableStream<T>({ start, write(chunk) { if (isFilled(inputValues)) controller.enqueue([chunk, ...inputValues]) }, abort(reason) { abortController.abort(reason) }, close() { controller.close() }, }) return { readable, writable } function start( controller: | ReadableStreamDefaultController<[T, ...ReadableStreamsChunks<RSs>]> | WritableStreamDefaultController ) { const onAbort = () => controller.error(abortController.signal.reason) abortController.signal.addEventListener('abort', onAbort) } }