type SimpleFunction = (arg: T) => void; type Return = Promise | T; async function toPromise(value: Return): Promise { return value instanceof Promise ? value : Promise.resolve(value); } /** * Create a promise which waits until `done` is called or until `timeout` period is reached. * If `timeout` is reached then this will throw an Error. * * @param task The task to wait for. * @param timeout The timeout period. */ export async function waitForTask( task: (done: SimpleFunction) => Return, timeoutMs: number = 2000 ): Promise { const timeoutPromise = new Promise((_, rej) => { const wait = setTimeout(() => { clearTimeout(wait); rej(new Error('Task timed out.')); }, timeoutMs); }); const taskPromise = new Promise(async (res, rej) => { try { await toPromise(task(res)); } catch (e) { rej(e); } }); return Promise.race([timeoutPromise, taskPromise]) as Promise; } export interface PollOptions { timeoutMs: number; interval: number; } /** * Creates a promise which calls the `task` every `interval` until `done` is called or until `timeout` period is reached. * If `timeout` is reached then this will throw an Error. * * @param task The task which runs every `interval` ms. * @param options The polling options. */ export async function poll( task: (done: SimpleFunction) => Return, options: Partial = {} ): Promise { const defaults: PollOptions = { timeoutMs: 2000, interval: 100, }; const { timeoutMs, interval } = { ...defaults, ...options, }; const endTime = Date.now() + timeoutMs; let stop = false; const finish = () => { stop = true; }; const _poll = async (resolve: any, reject: any) => { if (stop) { resolve(); } else if (Date.now() >= endTime) { finish(); reject(new Error('Periodic check timeout')); } else { try { await toPromise(task(finish)); } catch (e) { finish(); reject(e); return; } setTimeout(() => { void _poll(resolve, reject); }, interval); } }; return new Promise((resolve, reject) => { void _poll(resolve, reject); }); } /** * Creates a promise which waits until `check` returns `true` or rejects if `timeout` preiod is reached. * If `timeout` is reached then this will throw an Error. * * @param check The boolean check. * @param timeout The time before an error is thrown. */ export async function waitUntil( check: () => Return, timeoutMs: number = 2000 ) { // This is causing unhandled promise rejection somewhere in MessageQueue tests return poll( async done => { const result = await toPromise(check()); if (result) { done(); } }, { timeoutMs, interval: timeoutMs / 20, } ); } export async function timeout( promise: Promise, timeoutMs: number = 2000 ): Promise { const timeoutPromise = new Promise((_, rej) => { const wait = setTimeout(() => { clearTimeout(wait); rej(new Error('Task timed out.')); }, timeoutMs); }); return Promise.race([timeoutPromise, promise]); } export async function delay(timeoutMs: number = 2000): Promise { return new Promise(resolve => { setTimeout(() => { resolve(true); }, timeoutMs); }); }