export function delayedPromise(delay: number): Promise { return new Promise(resolve => setTimeout(resolve, delay)); } export function timeoutPromise(promise: Promise, ms: number, message = `timed out after ${ms}ms`): Promise { return new Promise(function (resolve, reject) { setTimeout(() => reject(new Error(message)), ms); promise.then(resolve, reject); }); } export interface RetryPromiseOptions { interval: number; retries: number; timeout?: number; timeoutMessage?: string; } const never: Promise = new Promise(() => void 0); export async function retryPromise(promiseProvider: () => Promise, {interval, retries, timeout, timeoutMessage = `timed out after ${timeout}ms`}: RetryPromiseOptions): Promise { if (!timeout && !retries) { return await promiseProvider(); } if (timeout && timeout <= retries * interval) { return Promise.reject(new Error(`timeout (${timeout}ms) must be greater than retries (${retries}) times interval (${interval}ms)`)); } const raceWithTimeout = [never, never]; if (timeout) { raceWithTimeout[1] = delayedPromise(timeout).then(() => Promise.reject(new Error(timeoutMessage))); } let iterations = retries ? retries + 1 : Number.MAX_SAFE_INTEGER; let lastError: Error | null = null; while (iterations-- > 0) { try { raceWithTimeout[0] = promiseProvider(); return await Promise.race(raceWithTimeout); } catch (e) { if (e.message === timeoutMessage) { throw lastError || e; } lastError = e; } await new Promise(resolve => setTimeout(resolve, interval)); } throw lastError || new Error(timeoutMessage); }