/** * @packageDocumentation * * Merge several (async)iterables into one, yield values as they arrive. * * Nb. sources are iterated over in parallel so the order of emitted items is not guaranteed. * * @example * * ```javascript * import merge from 'it-merge' * import all from 'it-all' * * // This can also be an iterator, generator, etc * const values1 = [0, 1, 2, 3, 4] * const values2 = [5, 6, 7, 8, 9] * * const arr = all(merge(values1, values2)) * * console.info(arr) // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 * ``` * * Async sources must be awaited: * * ```javascript * import merge from 'it-merge' * import all from 'it-all' * * // This can also be an iterator, async iterator, generator, etc * const values1 = async function * () { * yield * [0, 1, 2, 3, 4] * } * const values2 = async function * () { * yield * [5, 6, 7, 8, 9] * } * * const arr = await all(merge(values1(), values2())) * * console.info(arr) // 0, 1, 5, 6, 2, 3, 4, 7, 8, 9 <- nb. order is not guaranteed * ``` */ import { queuelessPushable } from 'it-queueless-pushable' import type { Pushable } from 'it-queueless-pushable' function isAsyncIterable (thing: any): thing is AsyncIterable { return thing[Symbol.asyncIterator] != null } async function addAllToPushable (sources: Array | Iterable>, output: Pushable, signal: AbortSignal): Promise { try { await Promise.all( sources.map(async (source) => { for await (const item of source) { await output.push(item, { signal }) signal.throwIfAborted() } }) ) await output.end(undefined, { signal }) } catch (err: any) { await output.end(err, { signal }) .catch(() => {}) } } async function * mergeSources (sources: Array | Iterable>): AsyncGenerator { const controller = new AbortController() const output = queuelessPushable() addAllToPushable(sources, output, controller.signal) .catch(() => {}) try { yield * output } finally { controller.abort() } } function * mergeSyncSources (syncSources: Array>): Generator { for (const source of syncSources) { yield * source } } /** * Treat one or more iterables as a single iterable. * * Nb. sources are iterated over in parallel so the * order of emitted items is not guaranteed. */ function merge (...sources: Array>): Generator function merge (...sources: Array | Iterable>): AsyncGenerator function merge (...sources: Array | Iterable>): AsyncGenerator | Generator { const syncSources: Array> = [] for (const source of sources) { if (!isAsyncIterable(source)) { syncSources.push(source) } } if (syncSources.length === sources.length) { // all sources are synchronous return mergeSyncSources(syncSources) } return mergeSources(sources) } export default merge