UNPKG

@reactivex/ix-esnext-esm

Version:

The Interactive Extensions for JavaScript

133 lines (131 loc) 5.71 kB
import { AsyncIterableX } from '../asynciterablex.js'; import { wrapWithAbort } from '../operators/withabort.js'; import { AbortError, throwIfAborted } from '../../aborterror.js'; import { safeRace } from '../../util/safeRace.js'; import { isPromise } from '../../util/isiterable.js'; import { returnAsyncIterator } from '../../util/returniterator.js'; const NEVER_PROMISE = new Promise(() => { }); function ignoreInnerAbortErrors(signal) { return function ignoreInnerAbortError(e) { if (signal.aborted && e instanceof AbortError) { return NEVER_PROMISE; } throw e; }; } async function* wrapIterator(source, index, type, signal) { throwIfAborted(signal); for await (const value of wrapWithAbort(source, signal)) { throwIfAborted(signal); yield { type, index, value }; } return { type, index, value: undefined }; } /** @ignore */ export class FlattenConcurrentAsyncIterable extends AsyncIterableX { _source; _selector; _concurrent; _switchMode; _thisArg; constructor(_source, _selector, _concurrent, _switchMode, _thisArg) { super(); this._source = _source; this._selector = _selector; this._concurrent = _concurrent; this._switchMode = _switchMode; this._thisArg = _thisArg; this._concurrent = this._switchMode ? 1 : Math.max(_concurrent, 1); } async *[Symbol.asyncIterator](outerSignal) { throwIfAborted(outerSignal); let active = 0; let outerIndex = 0; let outerComplete = false; const thisArg = this._thisArg; const selector = this._selector; const switchMode = this._switchMode; const concurrent = this._concurrent; const outerValues = new Array(0); const innerIndices = new Array(0); const controllers = new Array(isFinite(concurrent) ? concurrent : 0); const inners = new Array(isFinite(concurrent) ? concurrent : 0); const outer = wrapIterator(this._source, 0, 0 /* Type.OUTER */, outerSignal); const results = [outer.next()]; try { do { const { done = false, value: { type, value, index }, } = await safeRace(results); if (!done) { switch (type) { case 0 /* Type.OUTER */: { if (switchMode) { active = 0; } if (active < concurrent) { pullNextOuter(value); } else { outerValues.push(value); } results[0] = outer.next(); break; } case 1 /* Type.INNER */: { yield value; const { [index - 1]: inner } = inners; const { [index - 1]: { signal }, } = controllers; results[index] = inner.next().catch(ignoreInnerAbortErrors(signal)); break; } } } else { // ignore this result slot results[index] = NEVER_PROMISE; switch (type) { case 0 /* Type.OUTER */: { outerComplete = true; break; } case 1 /* Type.INNER */: { --active; // return the current slot to the pool innerIndices.push(index); // synchronously drain the `outerValues` buffer while (active < concurrent && outerValues.length) { // Don't use `await` so we avoid blocking while the number of active inner sequences is less than `concurrent`. pullNextOuter(outerValues.shift()); } break; } } } } while (!outerComplete || active + outerValues.length > 0); } finally { controllers.forEach((controller) => controller?.abort()); await Promise.all([outer, ...inners].map(returnAsyncIterator)); } function pullNextOuter(outerValue) { ++active; const index = innerIndices.pop() || active; // abort the current inner iterator first if (switchMode && controllers[index - 1]) { controllers[index - 1].abort(); } controllers[index - 1] = new AbortController(); const innerSignal = controllers[index - 1].signal; // Get the next inner sequence. // `selector` is a sync or async function that returns AsyncIterableInput. const inner = selector.call(thisArg, outerValue, outerIndex++, innerSignal); results[index] = wrapAndPullInner(index, innerSignal, inner).catch(ignoreInnerAbortErrors(innerSignal)); } function wrapAndPullInner(index, signal, inner) { if (isPromise(inner)) { return inner.then((inner) => wrapAndPullInner(index, signal, inner)); } return (inners[index - 1] = wrapIterator(AsyncIterableX.as(inner), index, 1 /* Type.INNER */, signal)).next(); } } } //# sourceMappingURL=_flatten.js.map