Skip to content

Commit af29661

Browse files
committed
feat: wait until condition met
1 parent 9f8b2bc commit af29661

File tree

2 files changed

+74
-4
lines changed

2 files changed

+74
-4
lines changed

electron/common/async/__tests__/async-utils.test.ts

+44-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createLogger } from '../../logger';
2-
import { runInBackground, sleep } from '../async.utils';
2+
import { runInBackground, sleep, waitUntil } from '../async.utils';
33

4-
jest.mock('../../logger/logger.utils', () => {
4+
jest.mock('../../logger', () => {
55
const actualModule = jest.requireActual('../../logger');
66
return {
77
...actualModule,
@@ -29,7 +29,7 @@ describe('async-utils', () => {
2929

3030
const end = Date.now();
3131

32-
expect(timeoutSpy).toBeCalledTimes(1);
32+
expect(timeoutSpy).toHaveBeenCalledTimes(1);
3333
expect(end - start).toBeGreaterThanOrEqual(sleepMillis - varianceMillis);
3434
expect(end - start).toBeLessThanOrEqual(sleepMillis + varianceMillis);
3535
});
@@ -72,4 +72,45 @@ describe('async-utils', () => {
7272
}, 250);
7373
});
7474
});
75+
76+
describe('#waitUntil', () => {
77+
test('when condition is true then it resolves true', async () => {
78+
const condition = jest.fn().mockReturnValue(true);
79+
const interval = 100;
80+
const timeout = 1000;
81+
82+
const result = await waitUntil({ condition, interval, timeout });
83+
84+
expect(result).toBe(true);
85+
expect(condition).toHaveBeenCalledTimes(1);
86+
});
87+
88+
test('when condition is false then it resolves false', async () => {
89+
const condition = jest.fn().mockReturnValue(false);
90+
const interval = 100;
91+
const timeout = 1000;
92+
93+
const result = await waitUntil({ condition, interval, timeout });
94+
95+
expect(result).toBe(false);
96+
expect(condition).toHaveBeenCalledTimes(9);
97+
});
98+
99+
test('when condition is true after 5 intervals then it resolves true', async () => {
100+
const condition = jest
101+
.fn()
102+
.mockReturnValueOnce(false)
103+
.mockReturnValueOnce(false)
104+
.mockReturnValueOnce(false)
105+
.mockReturnValueOnce(false)
106+
.mockReturnValueOnce(true);
107+
const interval = 100;
108+
const timeout = 1000;
109+
110+
const result = await waitUntil({ condition, interval, timeout });
111+
112+
expect(result).toBe(true);
113+
expect(condition).toHaveBeenCalledTimes(5);
114+
});
115+
});
75116
});

electron/common/async/async.utils.ts

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { createLogger } from '../logger/logger.utils';
1+
import * as rxjs from 'rxjs';
2+
import { createLogger } from '../logger';
23

34
const logger = createLogger('async:utils');
45

@@ -17,3 +18,31 @@ export const runInBackground = (fn: () => Promise<unknown>): void => {
1718
});
1819
});
1920
};
21+
22+
/**
23+
* Resolves true if the condition returns true before the timeout, else false.
24+
*/
25+
export const waitUntil = async (options: {
26+
/**
27+
* Evaluated each interval until it returns true or the timeout is reached.
28+
*/
29+
condition: () => boolean;
30+
/**
31+
* How often to evaluate the condition, in milliseconds.
32+
*/
33+
interval: number;
34+
/**
35+
* How long to wait before stop and return false, in milliseconds.
36+
*/
37+
timeout: number;
38+
}): Promise<boolean> => {
39+
const { condition, interval, timeout } = options;
40+
const poller$ = rxjs.interval(interval).pipe(
41+
rxjs.filter(() => condition()), // check if condition met yet
42+
rxjs.map(() => true), // convert interval number to true, condition met
43+
rxjs.take(1), // stop poller after conditions met
44+
rxjs.timeout(timeout), // abort if conditions not met in time
45+
rxjs.catchError(() => rxjs.of(false)) // convert timeout error to false
46+
);
47+
return rxjs.firstValueFrom(poller$); // resolves once a value is emitted
48+
};

0 commit comments

Comments
 (0)