Skip to content

Commit

Permalink
feat(util): add paced helper to break up heavy iterators (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
fallenoak authored Dec 25, 2023
1 parent 0493c70 commit a1498bb
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 2 deletions.
25 changes: 24 additions & 1 deletion src/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(iterable: Iterable<T>, 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 };
41 changes: 40 additions & 1 deletion src/spec/util.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { nextFrame } from '../lib';
import { nextFrame, paced, sleep } from '../lib/util';
import { describe, expect, test } from 'vitest';

describe('util', () => {
Expand All @@ -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);
});
});
});

0 comments on commit a1498bb

Please sign in to comment.