interface ReduceResult { done: true; value: T; } export function reduceLargeArray( arr: T[], processEachFn: (acc: R, x: T) => ReduceResult | R, initialValue: R, onCompleteFn: (r: R) => void ) { let frameTime = 1000 / 60; // 60 FPS let accumulator = initialValue; function isDone(value: unknown): value is ReduceResult { return value && value['done']; } function nextBatch(start: number) { let tstart = Date.now(); for (let i = start; i < arr.length; i++) { let tdelta = Date.now() - tstart; if (tdelta >= frameTime) { requestAnimationFrame(() => nextBatch(i)); break; } let resultOrDone = processEachFn(accumulator, arr[i]); if (isDone(resultOrDone)) { onCompleteFn(resultOrDone.value); break; } else if (i === arr.length - 1) { onCompleteFn(resultOrDone); break; } accumulator = resultOrDone; } } if (arr.length === 0) { onCompleteFn(accumulator); } else { nextBatch(0); } }