All files / src/utils concurrency.ts

100% Statements 15/15
100% Branches 5/5
100% Functions 3/3
100% Lines 15/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36                          1x   97x 97x 97x 97x 191x 208x 208x 208x   52x 6x 6x   46x       97x 91x    
/**
 * Run `work` over `items` with at most `limit` in flight, settling every one.
 *
 * Results come back in input order whatever order they complete in, so callers that merge them
 * stay deterministic.
 *
 * A failure that `isFatal` accepts is not settled: it rejects the whole run as soon as it is
 * seen, and no further item is started. Work already in flight finishes in the background.
 */
export async function settleWithConcurrency<T, R>(
	items: readonly T[],
	limit: number,
	work: (item: T) => Promise<R>,
	isFatal: (reason: unknown) => boolean = () => false,
): Promise<PromiseSettledResult<R>[]> {
	const results: PromiseSettledResult<R>[] = Array.from({ length: items.length });
	let next = 0;
	let stopped = false;
	const worker = async (): Promise<void> => {
		while (!stopped && next < items.length) {
			const index = next++;
			try {
				results[index] = { status: 'fulfilled', value: await work(items[index] as T) };
			} catch (reason) {
				if (isFatal(reason)) {
					stopped = true;
					throw reason;
				}
				results[index] = { status: 'rejected', reason };
			}
		}
	};
	await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
	return results;
}