From a1498bb57bbb5bb51507149a71a3ca9511327718 Mon Sep 17 00:00:00 2001 From: fallenoak Date: Mon, 25 Dec 2023 13:18:46 -0600 Subject: [PATCH] feat(util): add paced helper to break up heavy iterators (#2) --- src/lib/util.ts | 25 ++++++++++++++++++++++++- src/spec/util.spec.ts | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/lib/util.ts b/src/lib/util.ts index fee8c0e..504b511 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -8,4 +8,27 @@ const nextFrame = () => { }); }; -export { nextFrame }; +/** + * Returns an iterator for the given iterable that pauses for an animation frame every time the + * given pace has elapsed. Useful for pacing synchronous work without excessively blocking the + * main thread. + * + * @param iterable - Any iterable + * @param paceMs - Length of time in milliseconds before waiting for a frame. + */ +async function* paced(iterable: Iterable, paceMs = 10) { + let lastFrameMs = Date.now(); + + for (const it of iterable) { + if (Date.now() - lastFrameMs >= paceMs) { + await nextFrame(); + lastFrameMs = Date.now(); + } + + yield it; + } +} + +const sleep = (timeMs: number) => new Promise((resolve) => setTimeout(resolve, timeMs)); + +export { nextFrame, paced, sleep }; diff --git a/src/spec/util.spec.ts b/src/spec/util.spec.ts index c28c3bc..17f7ede 100644 --- a/src/spec/util.spec.ts +++ b/src/spec/util.spec.ts @@ -1,4 +1,4 @@ -import { nextFrame } from '../lib'; +import { nextFrame, paced, sleep } from '../lib/util'; import { describe, expect, test } from 'vitest'; describe('util', () => { @@ -7,4 +7,43 @@ describe('util', () => { await expect(nextFrame()).resolves.toEqual(undefined); }); }); + + describe('paced', () => { + test('should iterate over array in multiple frames when work takes longer than a frame', async () => { + let frames = 0; + const countFrame = () => { + frames++; + requestAnimationFrame(countFrame); + }; + requestAnimationFrame(countFrame); + + const elements = [1, 2, 3]; + const iterated = []; + for await (const element of paced(elements)) { + await sleep(30); + iterated.push(element); + } + + expect(iterated).toEqual(elements); + expect(frames).toBeGreaterThan(0); + }); + + test('should iterate over array in zero frames when work takes less than a frame', async () => { + let frames = 0; + const countFrame = () => { + frames++; + requestAnimationFrame(countFrame); + }; + requestAnimationFrame(countFrame); + + const elements = [1, 2, 3]; + const iterated = []; + for await (const element of paced(elements)) { + iterated.push(element); + } + + expect(iterated).toEqual(elements); + expect(frames).toBe(0); + }); + }); });