loop-controls
Version:
break/continue controls for loops and higher-order functions (sync, async, concurrent). Designed to compose with ts-pattern.
75 lines (68 loc) • 2.32 kB
text/typescript
import { _Break as BaseBreak, _Continue as BaseContinue, type Control } from "./core.js";
export async function forEachAsync<T>(
iter: AsyncIterable<T> | Iterable<T>,
fn: (item: T, control: Control, index: number) => Promise<void>
): Promise<{ broken: false } | { broken: true }> {
class LoopBreak<T = unknown> extends BaseBreak<T> {}
class LoopContinue extends BaseContinue {}
const $: Control = {
break: (value?: unknown): never => { throw new LoopBreak(value); },
continue: (): never => { throw new LoopContinue(); },
};
let i = 0;
for await (const item of iter) {
try {
await fn(item, $, i++);
} catch (e) {
if (e instanceof LoopContinue) continue;
if (e instanceof LoopBreak) return { broken: true };
throw e;
}
}
return { broken: false };
}
export async function reduceAsync<T, A>(
iter: AsyncIterable<T> | Iterable<T>,
seed: A,
fn: (acc: A, item: T, control: Control, index: number) => Promise<A>
): Promise<A> {
class LoopBreak<T = unknown> extends BaseBreak<T> {}
class LoopContinue extends BaseContinue {}
const $: Control = {
break: (value?: unknown): never => { throw new LoopBreak(value); },
continue: (): never => { throw new LoopContinue(); },
};
let acc = seed, i = 0;
for await (const item of iter) {
try {
acc = await fn(acc, item, $, i++);
} catch (e) {
if (e instanceof LoopContinue) continue;
if (e instanceof LoopBreak) return ((e as LoopBreak).value !== undefined ? (e as LoopBreak).value : acc) as A;
throw e;
}
}
return acc;
}
export async function findAsync<T>(
iter: AsyncIterable<T> | Iterable<T>,
fn: (item: T, control: Control, index: number) => Promise<boolean>
): Promise<T | undefined> {
class LoopBreak<T = unknown> extends BaseBreak<T> {}
class LoopContinue extends BaseContinue {}
const $: Control = {
break: (value?: unknown): never => { throw new LoopBreak(value); },
continue: (): never => { throw new LoopContinue(); },
};
let i = 0;
for await (const item of iter) {
try {
if (await fn(item, $, i++)) return item;
} catch (e) {
if (e instanceof LoopContinue) continue;
if (e instanceof LoopBreak) return (e as LoopBreak).value as T | undefined;
throw e;
}
}
return undefined;
}