import { isUndefined } from 'extra-utils' import { Awaitable } from 'justypes' import { isAsyncIterable } from '@src/is-async-iterable.js' export function reduceAsync( iterable: Iterable | AsyncIterable , fn: ( accumulator: Awaited , currentValue: Awaited , index: number ) => Awaitable> ): Promise> export function reduceAsync( iterable: Iterable | AsyncIterable , fn: (accumulator: Awaited, currentValue: Awaited, index: number) => Awaitable , initialValue: U ): Promise> export function reduceAsync( iterable: Iterable | AsyncIterable , fn: (( accumulator: Awaited , currentValue: Awaited , index: number ) => Awaitable>) & ((accumulator: Awaited, currentValue: Awaited, index: number) => Awaitable) , initialValue?: U ) { if (isUndefined(initialValue)) { return reduceAsyncWithoutInitialValue(iterable, fn) } else { return reduceAsyncWithInitialValue(iterable, fn, initialValue) } } async function reduceAsyncWithInitialValue( iterable: Iterable | AsyncIterable , fn: (accumulator: Awaited, currentValue: Awaited, index: number) => Awaitable , initialValue: U ): Promise { let result: Awaited = await initialValue , index = 0 for await (const currentValue of iterable) { result = await fn(result, currentValue, index++) } return result } function reduceAsyncWithoutInitialValue( iterable: Iterable | AsyncIterable , fn: ( accumulator: Awaited , currentValue: Awaited , index: number ) => Awaitable> ): Promise> { if (isAsyncIterable(iterable)) { return reduceAsyncIterable(iterable) } else { return reduceIterable(iterable) } async function reduceAsyncIterable(iterable: AsyncIterable): Promise> { const iterator = iterable[Symbol.asyncIterator]() let done: boolean | undefined try { let result: Awaited = await readInitialValue(iterator) let index = 1 let value: T while ({ value, done } = await iterator.next(), !done) { result = await fn(result, await value, index++) } return result } finally { if (!done) await iterator.return?.() } async function readInitialValue( iterator: AsyncIterator ): Promise { const result = await iterator.next() if (result.done) { done = true throw new Error('Reduce of empty iterable with no initial value') } return result.value } } async function reduceIterable(iterable: Iterable): Promise> { const iterator = iterable[Symbol.iterator]() let done: boolean | undefined try { let result: Awaited = await readInitialValue(iterator) let index = 1 let value: T while ({ value, done } = iterator.next(), !done) { result = await fn(result, await value, index++) } return result } finally { if (!done) iterator.return?.() } function readInitialValue(iterator: Iterator): T { const result = iterator.next() if (result.done) { done = true throw new Error('Reduce of empty iterable with no initial value') } return result.value } } }