interface RetryOptions { count?: number; interval: number; } interface Resolvable { promise: Promise; resolve: (value: T) => void; reject: (reason: any) => void; } export interface Retry { resolvable: Resolvable; cancel: () => void; } function createResolvable(): Resolvable { let resolve: Resolvable['resolve'] = () => { // }; let reject: Resolvable['reject'] = () => { // }; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject, }; } export default function retry(supplier: () => Promise, options: RetryOptions): Retry { let alive = true; let schedule: number; const resolvable = createResolvable(); const backoff = (timeout = 10, count = 0) => { const handle = (reason: any) => { if (!alive || (typeof options.count === 'number' && options.count <= count)) { resolvable.reject(reason); } else { schedule = window.setTimeout(() => { backoff(Math.max(10, Math.min(options.interval, timeout * 2)), count + 1); }, timeout); } }; try { supplier().then(resolvable.resolve, handle); } catch (reason) { handle(reason); } }; backoff(); return { resolvable, cancel: () => { if (schedule) { clearTimeout(schedule); } alive = false; }, }; }