/** * Async iteration over array-like and object structures * @module eachAsync */ import { isArrayLike } from './each.js'; /** * Type representing an async iteration callback function. * @template TError - Error type * @template T - Argument array type * @template TReturn - Return type */ export type NextFunction = (error?: TError, ...args: T) => TReturn; /** * Simplified version of NextFunction for cases without arguments. * @template T - Error type */ export type SimpleNextFunction = NextFunction>; /** * Represents an error that aggregates multiple errors from asynchronous iterations. * @class * @extends {Error} * @property {Error[]} errors - The array of errors that occurred during iteration. */ export class AggregateErrors extends Error { constructor(public readonly errors: Error[]) { super('One or more errors occurred. Please see errors field for more details'); } } /** * Asynchronously iterates over elements of an array-like structure. * @param {T[] | ArrayLike} array - Array-like structure to iterate over * @param {(element: T, i: number) => Promise} body - Async callback executed for each element * @param {boolean} waitForPrevious - Whether to wait for previous iteration to complete before next * @returns {Promise} Resolves when all iterations complete */ export async function array(array: T[] | ArrayLike, body: (element: T, i: number) => Promise, waitForPrevious: boolean): Promise { if (typeof waitForPrevious == 'undefined') waitForPrevious = true; if (waitForPrevious) for (let index = 0; index < array.length; index++) { const element = array[index]; await body(element, index); } else if (Array.isArray(array)) return Promise.all(array.map(body)).then(() => { }); else return Promise.all(Array.prototype.map.call(array, body)).then(() => { }); } /** * Asynchronously iterates over key-value pairs of an object. * @param {T} o - Object to iterate over * @param {(element: T[keyof T], i: keyof T) => Promise} body - Async callback for each key-value pair * @param {boolean} waitForPrevious - Whether to wait for previous iteration to complete * @returns {Promise} Resolves when all iterations complete */ export function object(o: T, body: (element: T[keyof T], i: keyof T) => Promise, waitForPrevious: boolean): Promise { if (typeof waitForPrevious == 'undefined') waitForPrevious = true; return array(Object.keys(o) as (keyof T)[], (key, i) => body(o[key], key), waitForPrevious); } /** * Unified async iteration entry point for arrays/objects. * @function * @param {T[] | ArrayLike | Record} it - Iterable structure to process * @param {(element: unknown, i: any) => Promise} body - Async iteration callback * @param {boolean} [waitForPrevious=true] - Whether to wait for previous iteration to complete * @returns {Promise} Resolves when all iterations complete */ export function each(array: T[] | ArrayLike, body: (element: T, i?: number) => Promise, waitForPrevious?: boolean): Promise export function each>(o: T, body: (element: T[typeof i], i: keyof T,) => Promise, waitForPrevious?: boolean): Promise // eslint-disable-next-line @typescript-eslint/no-explicit-any export function each(it: unknown[] | ArrayLike | Record, body: (element: unknown, i: any) => Promise, waitForPrevious?: boolean): Promise { if (typeof waitForPrevious === 'undefined') waitForPrevious = true; if (Array.isArray(it) || isArrayLike(it)) return array(it, body, waitForPrevious); return object, []>(it, body, waitForPrevious); } /** * Asynchronously maps elements of an array-like structure to new values. * @param {T[] | ArrayLike} it - Array-like structure to process * @param {(element: T, i: number) => Promise} body - Async transformation function * @param {boolean} waitForPrevious - Whether to wait for previous iteration * @returns {Promise} Promise resolving to transformed elements */ export async function mapArray(it: T[] | ArrayLike, body: (element: T, i: number) => Promise, waitForPrevious: boolean): Promise { const result = []; await array(it, async function (el, i) { result.push(await body(el, i)); }, waitForPrevious); return result; } /** * Asynchronously maps key-value pairs of an object to new values. * @param {TIn} o - Source object to process * @param {(element: TIn[keyof TIn], i: keyof TIn) => TResultValue} body - Transformation function * @param {boolean} asArray - Return results as array instead of object * @param {boolean} waitForPrevious - Whether to wait for previous iteration * @returns {Promise>>} Transformed results */ export function mapObject(o: TIn, body: (element: TIn[keyof TIn], i: keyof TIn) => TResultValue, asArray: true, waitForPrevious: boolean): Promise export function mapObject(o: TIn, body: (element: TIn[keyof TIn], i: keyof TIn) => TResultValue, asArray: false, waitForPrevious: boolean): Promise<{ [P in keyof TIn]?: TResultValue }> export function mapObject(o: TIn, body: (element: TIn[keyof TIn], i: keyof TIn) => TResultValue, asArray: boolean, waitForPrevious: boolean) // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export async function mapObject(o: TIn, body: (element: TIn[keyof TIn], i: keyof TIn) => TResultValue, asArray: boolean, waitForPrevious: boolean) { const result: Partial> = {}; const resultArray: TResultValue[] = []; await object(o, async function (el, i) { if (asArray) resultArray.push(await body(el, i)); else result[i] = await body(el, i); }, waitForPrevious); if (asArray) return resultArray; return result; } /** * Unified async mapping function for arrays/objects. * @function * @param {T[] | ArrayLike | Record} it - Iterable structure to process * @param {(element: any, i: any) => any} body - Async mapping function * @param {boolean} [asArray=false] - Return results as array * @param {boolean} [waitForPrevious=true] - Whether to wait between iterations * @returns {Promise} Transformed results based on input type */ export function map(array: T[] | ArrayLike, body: (element: T, i: number) => Promise, asArray: true, waitForPrevious?: boolean): Promise export function map(array: T[] | ArrayLike, body: (element: T, i: number) => Promise, asArray: boolean, waitForPrevious?: boolean): Promise export function map(o: TIn, body: (element: TIn[TKey], i: TKey) => Promise, asArray?: false, waitForPrevious?: boolean): Promise> export function map(o: TIn, body: (element: TIn[TKey], i: TKey) => Promise, asArray: true, waitForPrevious?: boolean): Promise // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any export function map(it: any, body: (element: any, i: any) => any, asArray?: boolean, waitForPrevious?: boolean) { if (isArrayLike(it)) return mapArray(it, body, asArray); return mapObject(it, body, asArray, waitForPrevious); } /** * Asynchronously filters elements of an array-like structure. * @param {T[] | ArrayLike} it - Array-like structure to filter * @param {(element: T, i: number) => Promise} body - Async predicate function * @param {boolean} waitForPrevious - Whether to wait for previous iteration * @returns {Promise} Filtered elements */ export async function grepArray(it: T[] | ArrayLike, body: (element: T, i: number) => Promise, waitForPrevious: boolean): Promise { const result = []; await array(it, async function (el, i) { if (await body(el, i)) result.push(el); }, waitForPrevious); return result; } // export type Partial = {[P in keyof T]?: T[P]} export type Proxy = { [P in keyof T]: U } /** * Asynchronously filters key-value pairs of an object. * @param {T} o - Object to filter * @param {(element: T[keyof T], i: keyof T) => Promise} body - Async predicate function * @param {boolean} asArray - Return results as array * @param {boolean} waitForPrevious - Whether to wait for previous iteration * @returns {Promise>} Filtered results */ export function grepObject(o: T, body: (element: T[keyof T], i: keyof T) => Promise, asArray: true, waitForPrevious: boolean): Promise export function grepObject(o: T, body: (element: T[keyof T], i: keyof T) => Promise, asArray: false, waitForPrevious: boolean): Promise> export function grepObject(o: T, body: (element: T[keyof T], i: keyof T) => Promise, asArray: boolean, waitForPrevious: boolean): Promise | T[keyof T][]> // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export async function grepObject(o: T, body: (element: T[keyof T], i: keyof T) => Promise, asArray: boolean, waitForPrevious: boolean) { const result: Partial = {}; const resultArray: T[keyof T][] = []; await object(o, async function (el, i) { if (await body(el, i)) if (asArray) resultArray.push(el); else result[i] = el; }, waitForPrevious); if (asArray) return resultArray; return result; } /** * Unified async filter function for arrays/objects. * @function * @param {T[] | ArrayLike | Record} it - Iterable structure to process * @param {(element: any, i: any) => Promise} body - Async predicate function * @param {boolean} [asArray=false] - Return results as array * @param {boolean} [waitForPrevious=true] - Whether to wait between iterations * @returns {Promise} Filtered results based on input type */ export function grep(array: T[] | ArrayLike, body: (element: T, i: number) => Promise, waitForPrevious?: boolean): Promise export function grep(o: T, body: (element: T[U], i: U) => Promise, waitForPrevious?: boolean): Promise> export function grep(o: T, body: (element: T[U], i: U) => Promise, asArray: true, waitForPrevious?: boolean): Promise // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any export function grep(it: any, body: (element: any, i: any) => Promise, asArray?: boolean, waitForPrevious?: boolean) { if (isArrayLike(it)) return grepArray(it, body, asArray); return grepObject(it, body, asArray, waitForPrevious); }