Skip to content

Commit 5dbb9cf

Browse files
committed
feat(helpers): add poll function
1 parent e077bad commit 5dbb9cf

File tree

3 files changed

+114
-26
lines changed

3 files changed

+114
-26
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ index.*.*js*
44
array.*.*js*
55
string.*.*js*
66
types
7-
Support
7+
/Support
88
*.tgz
99

1010
# in case gh-pages manually managed

src/Support/function/poll.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export default async function poll<T>(
2+
fn: () => Promise<T>,
3+
wait: number | ((result: T, attempts: number) => number) = 0,
4+
until?: Date | ((result: T, attempts: number) => boolean)
5+
): Promise<T> {
6+
let attempts = 0;
7+
8+
return new Promise((resolve, reject) => {
9+
const check = async () => {
10+
try {
11+
attempts++;
12+
const result = await fn();
13+
14+
if (until && (until instanceof Date ? new Date() >= until : until(result, attempts))) {
15+
resolve(result);
16+
return;
17+
}
18+
19+
setTimeout(() => void check(), typeof wait === 'function' ? wait(result, attempts) : wait);
20+
} catch (err: unknown) {
21+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
22+
reject(err);
23+
}
24+
};
25+
26+
void check();
27+
});
28+
}

tests/Support/function.test.ts

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -331,42 +331,102 @@ describe('function helpers', () => {
331331

332332
describe('poll()', () => {
333333
it('should poll indefinitely', async () => {
334+
jest.useRealTimers();
334335
const timesToRun = Math.floor(Math.random() * 10) + 1;
335336
let timesRan = 0;
336337

337-
const asyncFunc = jest.fn(async () => new Promise(resolve => {
338+
const asyncFunc = async () => new Promise(resolve => {
338339
// eslint-disable-next-line jest/no-conditional-in-test
339340
if (timesRan === timesToRun) {
340341
throw new Error('Done');
341342
}
342343

343344
timesRan++;
345+
resolve('Not yet');
346+
});
347+
348+
await func.poll(asyncFunc).catch(() => {});
349+
350+
expect(timesRan).toBe(timesToRun);
351+
});
352+
353+
it('should reject if any attempts fail', async () => {
354+
jest.useRealTimers();
355+
const asyncFunc = jest.fn(async () => new Promise(() => {
356+
throw new Error('Done');
357+
}));
358+
359+
await expect(func.poll(asyncFunc)).rejects.toThrow('Done');
360+
});
361+
362+
it('should wait the specified number of milliseconds', async () => {
363+
const start = jest.useFakeTimers({ advanceTimers: true }).now();
364+
let timesRan = 0;
365+
const asyncFunc = jest.fn(async () => new Promise(resolve => {
366+
timesRan++;
367+
368+
// eslint-disable-next-line jest/no-conditional-in-test
369+
if (timesRan === 2) {
370+
throw new Error('Done');
371+
}
372+
344373
resolve('Not yet');
345374
}));
346375

347-
await func.poll(asyncFunc, 10);
348-
349-
expect(asyncFunc).toHaveBeenCalledTimes(timesToRun);
350-
});
351-
352-
// it('should reject if any attempts fail', () => {
353-
//
354-
// });
355-
//
356-
// it('should wait the specified number of seconds', () => {
357-
//
358-
// });
359-
//
360-
// it('should wait the specified number of seconds returned from the wait function', () => {
361-
//
362-
// });
363-
//
364-
// it('should poll until the given date', () => {
365-
//
366-
// });
367-
//
368-
// it('should poll until the until argument returns true', () => {
369-
//
370-
// });
376+
await func.poll(asyncFunc, 100).catch(() => {});
377+
378+
expect(asyncFunc).toHaveBeenCalledTimes(2);
379+
expect(jest.useFakeTimers().now() - start).toBeGreaterThanOrEqual(100);
380+
});
381+
382+
it('should wait the specified number of seconds returned from the wait function', async () => {
383+
const start = jest.useFakeTimers({ advanceTimers: true }).now();
384+
let timesRan = 0;
385+
const asyncFunc = jest.fn(async () => new Promise(resolve => {
386+
timesRan++;
387+
388+
// eslint-disable-next-line jest/no-conditional-in-test
389+
if (timesRan === 3) {
390+
throw new Error('Done');
391+
}
392+
393+
resolve('Not yet');
394+
}));
395+
396+
await func.poll(
397+
asyncFunc,
398+
// wait for 100ms on the first attempt, 200ms on second and the 3rd attempt exits
399+
(_result, attempts) => attempts * 100
400+
).catch(() => {});
401+
402+
expect(asyncFunc).toHaveBeenCalledTimes(3);
403+
expect(jest.useFakeTimers().now() - start).toBeGreaterThanOrEqual(300);
404+
});
405+
406+
it('should poll until the given date', async () => {
407+
const start = jest.useFakeTimers({ advanceTimers: true }).now();
408+
const asyncFunc = jest.fn(async () => new Promise(resolve => resolve('Not yet')));
409+
410+
await func.poll(
411+
asyncFunc,
412+
0,
413+
new Date(start + 100)
414+
).catch(() => {});
415+
416+
expect(jest.useFakeTimers().now() - start).toBeGreaterThanOrEqual(100);
417+
});
418+
419+
it('should poll until the until argument returns true', async () => {
420+
jest.useFakeTimers({ advanceTimers: true });
421+
const asyncFunc = jest.fn(async () => new Promise(resolve => resolve('Not yet')));
422+
423+
await func.poll(
424+
asyncFunc,
425+
0,
426+
(_result, attempts) => attempts === 3
427+
).catch(() => {});
428+
429+
expect(asyncFunc).toHaveBeenCalledTimes(3);
430+
});
371431
});
372432
});

0 commit comments

Comments
 (0)