UNPKG

monoflux

Version:

Stream processing for JS simplified

581 lines 19.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Flux = void 0; class Flux { generator; upstream; handleCancel; _closed = false; constructor(generator, upstream, handleCancel) { const thiss = this; this.generator = (async function* gen() { try { yield* generator; } catch (e) { yield ignorableValue; throw e; } finally { thiss._closed = true; } })(); this.upstream = upstream; this.handleCancel = handleCancel; } //// Start AsyncGenerator methods //// [Symbol.asyncIterator]() { return this; } async next(...args) { const value = await this.generator.next(args); return value.value !== ignorableValue ? value : await this.next(args); } return(value) { return this.generator.return(value); } throw(e) { return this.generator.throw(e); } async cancel(e) { if (this.closed) { return; } if (this.upstream !== undefined) { return this.upstream.cancel(e); } else { if (this.handleCancel !== undefined) { this.handleCancel(e); } else { await this.throw(e); } } } //// End AsyncGenerator methods //// //// Start Promise methods //// async then(onfulfilled, onrejected) { try { const result = await this.asList(); return onfulfilled !== undefined && onfulfilled !== null ? await onfulfilled(result) : result; } catch (e) { if (onrejected !== undefined && onrejected !== null) { return onrejected(e); } else { throw e; } } } async catch(onrejected) { try { return this.asList(); } catch (e) { if (onrejected !== undefined && onrejected !== null) { return onrejected(e); } else { throw e; } } } async finally(onfinally) { const promise = await this.asList(); onfinally?.(); return promise; } get [Symbol.toStringTag]() { return 'Flux'; } //// End Promise methods //// subscribe(callback) { const flux = callback !== undefined ? this.doOnEach(callback) : this; const recur = () => { flux .next() .then(value => { if (!value.done) { recur(); } }) .catch(err => { console.error(err); }); }; recur(); return { unsubscribe: () => this.return() }; } filter(predicate) { const thiss = this; return Flux.constructFromGeneratorFunction(async function* gen() { for await (const value of thiss) { if (predicate(value)) { yield value; } } }, this); } /** * Flux until predicate is true; the rest is dropped. The first * value that is dropped is the value for which the predicate * is true. * * @param predicate */ untilExcl(predicate) { const thiss = this; return Flux.constructFromGeneratorFunction(async function* gen() { for await (const value of thiss) { if (predicate(value)) { break; } yield value; } }, this); } doOnEach(callback) { const thiss = this; return Flux.constructFromGeneratorFunction(async function* gen() { for await (const value of thiss) { callback(value); yield value; } }, this); } doAfterLast(callback) { const thiss = this; const events = []; return Flux.constructFromGeneratorFunction(async function* gen() { for await (const value of thiss) { events.push(value); yield value; } await callback(events); }, this); } map(mapper) { const thiss = this; return Flux.constructFromGeneratorFunction(async function* gen() { for await (const value of thiss) { yield mapper(value); } }, this); } take(n) { const thiss = this; return Flux.constructFromGeneratorFunction(async function* gen() { let i = 0; for await (const value of thiss) { if (i >= n) { thiss.cancel(); break; } i++; yield value; } }, this); } /** * Delay each of this Flux elements (Subscriber.onNext signals) by a given duration. * Signals are delayed and continue on the parallel default Scheduler, but empty * sequences or immediate error signals are not delayed. * * @param delayMillis - The duration in milliseconds to delay each element emission * @returns A new Flux with delayed element emissions * * @example * ```typescript * const flux = Flux.fromArray([1, 2, 3]) * const delayed = flux.delayElements(1000) // Each element delayed by 1 second * for await (const value of delayed) { * console.log(value) // Prints 1, 2, 3 with 1 second delay between each * } * ``` */ delayElements(delayMillis) { const thiss = this; return Flux.constructFromGeneratorFunction(async function* gen() { for await (const value of thiss) { await new Promise(resolve => setTimeout(resolve, delayMillis)); yield value; } }, this); } flatMap(mapper, options) { const thiss = this; return Flux.constructFromGeneratorFunction(async function* gen() { // Get first value to determine type const iterator = thiss[Symbol.asyncIterator](); const firstValue = await iterator.next(); if (firstValue.done) { return; } const firstResult = mapper(firstValue.value); const concurrency = options?.concurrency; if (Array.isArray(firstResult)) { yield* flatMapArrayImpl(mapper, firstValue.value, firstResult, iterator); } else if (firstResult instanceof Flux) { yield* flatMapFluxImpl(mapper, firstValue.value, firstResult, iterator, concurrency); } else { yield* flatMapPromiseImpl(mapper, firstValue.value, firstResult, iterator, concurrency); } }, this); } transform(defineGenerator) { const definedGenerator = defineGenerator(this); return Flux.constructFromGeneratorFunction(async function* gen() { for await (const value of definedGenerator) { yield value; } }, this); } async reduce(reducer, initialValue) { let reduction = initialValue; for await (const value of this) { reduction = reducer(reduction, value); } return reduction; } async asList() { const result = []; for await (const value of this) { result.push(value); } return result; } async whenComplete() { for await (const ignored of this) { // do nothing } } static create(creator) { const values = []; const resolvers = []; let isDone = false; let error = undefined; const push = (value) => { if (isDone) { throw new Error("Cannot push to a completed generator"); } if (resolvers.length > 0) { const resolve = resolvers.shift(); resolve.resolve({ done: false, value }); } else { values.push(value); } }; const complete = () => { isDone = true; while (resolvers.length > 0) { const resolve = resolvers.shift(); resolve.resolve({ done: true, value: undefined }); } }; const reject = (err) => { error = { value: err }; // Wake up next resolver to propagate the error resolvers.shift()?.reject(err); // This will make the `.next()` promise reject with the error }; (async () => { try { await creator(push, complete, reject); if (!isDone || error === undefined) { complete(); } } catch (err) { reject(err); } })(); return Flux.fromGenerator({ [Symbol.asyncIterator]() { return this; }, next(...args) { if (error) { const err = error.value; error = undefined; return Promise.reject(err); } if (values.length > 0) { const value = values.shift(); return Promise.resolve({ done: false, value }); } if (isDone) { return Promise.resolve({ done: true, value: undefined }); } return new Promise((resolve, reject) => { resolvers.push({ resolve, reject }); }); }, return() { complete(); return Promise.resolve({ done: true, value: undefined }); }, throw(err) { reject(err); return Promise.reject(err); }, }); } static just(...array) { return Flux.from(array); } static from(source, handleCancel) { if (Array.isArray(source)) { return Flux.fromArray(source); } else if (typeof source === 'function') { return Flux.fromGeneratorFunction(source, handleCancel); } else if (source instanceof ReadableStream) { return Flux.fromReadableStream(source, handleCancel); } else if (isGenerator(source)) { return Flux.fromGenerator(source, handleCancel); } else { throw new TypeError('Source must be an Array, AsyncGenerator, ReadableStream, or a generator function'); } } static fromArray(array) { return Flux.fromGeneratorFunction(async function* gen() { for (const value of array) { yield value; } }); } static constructFromGeneratorFunction(fn, upstream, handleCancel) { return new Flux(fn(), upstream, handleCancel); } static fromGeneratorFunction(fn, handleCancel) { return Flux.constructFromGeneratorFunction(fn, undefined, handleCancel); } static fromGenerator(generator, handleCancel) { return new Flux(generator, undefined, handleCancel); } static fromReadableStream(stream, handleCancel) { return Flux.fromGeneratorFunction(async function* gen() { const reader = stream.getReader(); try { let excerpt = undefined; while (!(excerpt = await reader.read()).done) { yield excerpt.value; } } finally { await reader.cancel(); } }, handleCancel); } get closed() { return this._closed; } } exports.Flux = Flux; function flatMapArrayImpl(mapper, firstValue, firstResult, iterator) { return (async function* () { // Yield first result for (const item of firstResult) { yield item; } // Continue with remaining values let next = await iterator.next(); while (!next.done) { const result = mapper(next.value); for (const item of result) { yield item; } next = await iterator.next(); } })(); } function flatMapPromiseImpl(mapper, firstValue, firstPromise, iterator, concurrency) { return (async function* () { const pendingPromises = []; const resolvedValues = []; const resolvedErrors = []; const resolvedIndexes = new Set(); let yieldedUpTo = 0; let nextIndex = 0; let sourceComplete = false; let runningCount = 0; // Helper to start a promise for a value const startPromise = (value, index, promise) => { const p = promise || mapper(value); pendingPromises[index] = p; p.then(resolved => { resolvedValues[index] = resolved; resolvedIndexes.add(index); if (concurrency !== undefined) { runningCount--; } }).catch(error => { resolvedErrors[index] = error; resolvedIndexes.add(index); if (concurrency !== undefined) { runningCount--; } }); if (concurrency !== undefined) { runningCount++; } }; // Start first promise startPromise(firstValue, nextIndex++, firstPromise); // Process iterator in background const processIterator = async () => { let next = await iterator.next(); while (!next.done) { // Wait if concurrency limit reached if (concurrency !== undefined) { while (runningCount >= concurrency) { await new Promise(resolve => setTimeout(resolve, 0)); } } startPromise(next.value, nextIndex++); next = await iterator.next(); } sourceComplete = true; }; // Start processing iterator in background const iteratorPromise = processIterator(); // Yield results in order as they become available while (true) { // Wait for next result to be available while (!resolvedIndexes.has(yieldedUpTo)) { // Check if we're done if (sourceComplete && yieldedUpTo >= pendingPromises.length) { return; } await new Promise(resolve => setTimeout(resolve, 0)); } // Check if we're done if (sourceComplete && yieldedUpTo >= pendingPromises.length) { break; } // Yield the next result if (resolvedErrors[yieldedUpTo] !== undefined) { throw resolvedErrors[yieldedUpTo]; } if (resolvedValues[yieldedUpTo] !== undefined) { yield resolvedValues[yieldedUpTo]; } yieldedUpTo++; } // Wait for iterator to complete await iteratorPromise; })(); } function flatMapFluxImpl(mapper, firstValue, firstFlux, iterator, concurrency) { return (async function* () { // Collect all values first const allValues = [firstValue]; let next = await iterator.next(); while (!next.done) { allValues.push(next.value); next = await iterator.next(); } const pendingFluxes = new Array(allValues.length); const buffers = new Map(); const completedIndexes = new Set(); let yieldedUpTo = 0; if (concurrency === undefined) { // No concurrency limit - start all fluxes immediately for (let i = 0; i < allValues.length; i++) { const index = i; // Capture the index value const flux = index === 0 ? firstFlux : mapper(allValues[index]); pendingFluxes[index] = flux; buffers.set(index, []); (async () => { try { for await (const item of flux) { buffers.get(index).push(item); } } catch (error) { buffers.get(index).push({ __error: error }); } finally { completedIndexes.add(index); } })(); } } else { // Concurrency limited - use semaphore pattern let currentIndex = 0; let runningCount = 0; const startNext = () => { while (runningCount < concurrency && currentIndex < allValues.length) { const index = currentIndex; currentIndex++; runningCount++; const flux = index === 0 ? firstFlux : mapper(allValues[index]); pendingFluxes[index] = flux; buffers.set(index, []); (async () => { try { for await (const item of flux) { buffers.get(index).push(item); } } catch (error) { buffers.get(index).push({ __error: error }); } finally { completedIndexes.add(index); runningCount--; startNext(); } })(); } }; startNext(); } // Yield results in order while (yieldedUpTo < pendingFluxes.length) { const buffer = buffers.get(yieldedUpTo); // Keep processing this flux until it's completed AND buffer is empty while (!completedIndexes.has(yieldedUpTo) || buffer.length > 0) { // Wait for buffer to have items or for flux to complete while (buffer.length === 0 && !completedIndexes.has(yieldedUpTo)) { await new Promise(resolve => setTimeout(resolve, 1)); } // Yield all items currently in buffer while (buffer.length > 0) { const item = buffer.shift(); if (item && typeof item === 'object' && '__error' in item) { throw item.__error; } yield item; } } yieldedUpTo++; } })(); } function isGenerator(obj) { return obj !== null && typeof obj === 'object' && typeof obj.next === 'function' && typeof obj.return === 'function' && typeof obj.throw === 'function' && Symbol.asyncIterator in obj; } const ignorableValue = {}; //# sourceMappingURL=Flux.js.map