diff --git a/src/wait.ts b/src/wait.ts index 6b393af..bc49d8d 100644 --- a/src/wait.ts +++ b/src/wait.ts @@ -52,7 +52,7 @@ export type WaitReturn = { [P in `reset${Name}`]: ReadyStatusSetter; }; -export const wait = (name?: Name): WaitReturn => { +export function wait(name?: Name): WaitReturn { let readyStatus: ReadyStatusEnum = ReadyStatusEnum.Pending; let resultValue: T; let waitPromise: Promise; @@ -127,4 +127,4 @@ export const wait = (name?: Name): WaitReturn; -}; +} diff --git a/src/waitFor.spec.ts b/src/waitFor.spec.ts new file mode 100644 index 0000000..395ae58 --- /dev/null +++ b/src/waitFor.spec.ts @@ -0,0 +1,35 @@ +import { waitFor } from './waitFor'; + +describe('waitFor()', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + it('resolves after second check', () => { + const check = jest.fn(); + check.mockReturnValueOnce(false); + check.mockReturnValueOnce(true); + + const result = waitFor(check); + jest.runAllTimers(); + + expect(check).toHaveBeenCalledTimes(2); + expect(setTimeout).toHaveBeenCalledTimes(1); + return expect(result).resolves.toEqual(true); + }); + + it('rejects when check throws error', () => { + const check = jest.fn(); + check.mockReturnValueOnce(false); + check.mockImplementationOnce(() => { + throw new Error('check error'); + }); + + const result = waitFor(check); + jest.runAllTimers(); + + expect(check).toHaveBeenCalledTimes(2); + expect(setTimeout).toHaveBeenCalledTimes(1); + return expect(result).rejects.toEqual(new Error('check error')); + }); +}); diff --git a/src/waitFor.ts b/src/waitFor.ts new file mode 100644 index 0000000..74f7ae7 --- /dev/null +++ b/src/waitFor.ts @@ -0,0 +1,48 @@ +import { wait } from './wait'; + +/** + * check function that returns check result + * falsy values means not ready, truthy values will be used to resolve the promise + */ +type WaitForCheckFun = () => T; + +interface WaitForOptions { + /** + * the interval(ms) for each check, default to 200ms + */ + checkInterval?: number; + /** + * throw a timeout error and reject the promise after given timeout(ms), + * default is no timeout + */ + timeout?: number; +} + +export function waitFor(check: WaitForCheckFun, options?: WaitForOptions): Promise { + const { checkInterval = 200, timeout } = options ?? {}; + + const { afterReady, setReady, setFailed } = wait(); + + const startTimestamp = Date.now(); + const doCheck = () => { + try { + const result = check(); + if (result) { + setReady(result); + return; + } + + if (timeout && Date.now() - startTimestamp > timeout) { + setFailed((new Error('timeout') as unknown) as T); + return; + } + + setTimeout(doCheck, checkInterval); + } catch (err) { + setFailed(err); + } + }; + doCheck(); + + return afterReady(); +}