import type {Result} from '../result/models'; import {getPromiseOptions, getPromisesOptions, getResultsFromPromises} from './helpers'; import {handleResult, settlePromise} from './misc'; import { PROMISE_ABORT_OPTIONS, PROMISE_ABORT_EVENT, PROMISE_MESSAGE_EXPECTATION_ATTEMPT, PROMISE_STRATEGY_DEFAULT, PROMISE_TYPE_FULFILLED, PROMISE_TYPE_REJECTED, type PromiseData, type PromiseHandlers, type PromiseOptions, type PromisesItems, type PromisesOptions, type PromisesResult, type PromisesValue, type PromisesValues, type PromisesUnwrapped, } from './models'; import {getTimedPromise} from './timed'; // #region Functions /** * Wrap a _Promise_ with safety handlers, with optional abort capabilities and timeout * * @param promise _Promise_ to wrap * @param options Options for the _Promise_ * @returns Wrapped _Promise_ */ export async function attemptPromise( promise: Promise, options?: PromiseOptions | AbortSignal | number, ): Promise; /** * Wrap a _Promise_-returning callback with safety handlers, with optional abort capabilities and timeout * * @param callback Callback to wrap * @param options Options for the _Promise_ * @returns _Promise_-wrapped callback */ export async function attemptPromise( callback: () => Promise, options?: PromiseOptions | AbortSignal | number, ): Promise; /** * Wrap a callback with a _Promise_ and safety handlers, with optional abort capabilities and timeout * * @param callback Callback to wrap * @param options Options for the _Promise_ * @returns _Promise_-wrapped callback */ export async function attemptPromise( callback: () => Value, options?: PromiseOptions | AbortSignal | number, ): Promise; export async function attemptPromise( value: (() => Value) | Promise, options?: PromiseOptions | AbortSignal | number, ): Promise { const isFunction = typeof value === 'function'; if (!isFunction && !(value instanceof Promise)) { return Promise.reject(new TypeError(PROMISE_MESSAGE_EXPECTATION_ATTEMPT)); } const {signal, time} = getPromiseOptions(options); if (signal?.aborted ?? false) { return Promise.reject(signal!.reason); } function abort(): void { rejector(signal!.reason); } async function handler( resolve: (value: Value) => void, reject: (reason: unknown) => void, ): Promise { try { let result = isFunction ? value() : await value; if (result instanceof Promise) { result = await result; } settlePromise(abort, resolve, result, signal); } catch (error) { settlePromise(abort, reject, error, signal); } } let rejector: (reason: unknown) => void; signal?.addEventListener(PROMISE_ABORT_EVENT, abort, PROMISE_ABORT_OPTIONS); const promise = new Promise((resolve, reject) => { rejector = reject; void handler(resolve, reject); }); return time > 0 ? getTimedPromise(promise, time, signal) : promise; } /** * Handle a list of _Promises_, returning their results in an ordered array * * Depending on the strategy, the function will either reject on the first error encountered or return an array of rejected and resolved results * * @param items List of _Promises_ * @param options Options for handling the _Promises_ * @returns List of results */ export async function promises( items: [...Items], options?: Options, ): Promise< Options['strategy'] extends 'first' ? PromisesUnwrapped : PromisesValues> >; /** * Handle a list of _Promises_, returning their results in an ordered array * * Depending on the strategy, the function will either reject on the first error encountered or return an array of rejected and resolved results * * @param items List of _Promises_ * @param options Options for handling the _Promises_ * @returns List of results */ export async function promises( items: Promise[], options?: Options, ): Promise[]>; /** * Handle a list of _Promises_, returning their results in an ordered array * * If any _Promise_ in the list is rejected, the whole function will reject * * @param items List of _Promises_ * @param strategy Strategy for handling the _Promises_; rejects on the first error encountered * @returns List of results */ export async function promises( items: [...Items], strategy: 'first', ): Promise>; /** * Handle a list of _Promises_, returning their results in an ordered array * * If any _Promise_ in the list is rejected, the whole function will reject * * @param items List of _Promises_ * @param strategy Strategy for handling the _Promises_; rejects on the first error encountered * @returns List of results */ export async function promises(items: Promise[], strategy: 'first'): Promise; /** * Handle a list of _Promises_, returning their results in an ordered array of rejected and resolved results * * @param items List of _Promises_ * @param signal AbortSignal for aborting the operation _(when aborted, the _Promise_ will reject with the reason of the signal)_ * @returns List of results */ export async function promises( items: [...Items], signal?: AbortSignal, ): Promise>>; /** * Handle a list of _Promises_, returning their results in an ordered array of rejected and resolved results * * @param items List of _Promises_ * @param signal AbortSignal for aborting the operation _(when aborted, the _Promise_ will reject with the reason of the signal)_ * @returns List of results */ export async function promises( items: Array | (() => Promise)>, signal?: AbortSignal, ): Promise[]>; export async function promises(items: unknown[], options?: unknown): Promise { const {signal, strategy} = getPromisesOptions(options); if (signal?.aborted ?? false) { return Promise.reject(signal!.reason); } if (!Array.isArray(items)) { return Promise.resolve([]); } const actual = items .map(item => (typeof item === 'function' ? item() : item)) .filter(item => item instanceof Promise); const {length} = actual; if (length === 0) { return Promise.resolve([]); } const complete = strategy === PROMISE_STRATEGY_DEFAULT; function abort(): void { handlers.reject(signal!.reason); } signal?.addEventListener(PROMISE_ABORT_EVENT, abort, PROMISE_ABORT_OPTIONS); const data: PromiseData = { last: length - 1, result: [] as unknown[], }; let handlers: PromiseHandlers; return new Promise((resolve, reject) => { handlers = {reject, resolve}; for (let index = 0; index < length; index += 1) { void actual[index] .then(value => handleResult(PROMISE_TYPE_FULFILLED, { abort, complete, data, handlers, index, signal, value, }), ) .catch(reason => handleResult(PROMISE_TYPE_REJECTED, { abort, complete, data, handlers, index, signal, value: reason, }), ); } }); } promises.result = resultPromises; /** * Handle a list of _Promises_, returning their results in an ordered array of results _({@link Result})_ * * Depending on the strategy, the function will either reject on the first error encountered or return an array of rejected and resolved results * * _Available as `resultPromises` and `promises.result`_ * * @param items List of _Promises_ * @param signal AbortSignal for aborting the operation _(when aborted, the _Promise_ will reject with the reason of the signal)_ * @returns List of results */ export async function resultPromises( items: [...Items], signal?: AbortSignal, ): Promise>>; /** * Handle a list of _Promises_, returning their results in an ordered array of results _({@link Result})_ * * Depending on the strategy, the function will either reject on the first error encountered or return an array of rejected and resolved results * * _Available as `resultPromises` and `promises.result`_ * * @param items List of _Promises_ * @param signal AbortSignal for aborting the operation _(when aborted, the _Promise_ will reject with the reason of the signal)_ * @returns List of results */ export async function resultPromises( items: Promise[], signal?: AbortSignal, ): Promise>[]>; export async function resultPromises( items: Promise[], signal?: AbortSignal, ): Promise[]> { return promises(items, signal).then(getResultsFromPromises); } // #endregion