// @flow type AsyncRetryOptions = { maxRetries?: ?number, expoBackoffMs?: ?number, asyncErrorHandler?: ?(err: Error, triesExecuted: number) => Promise } export async function sleep(periodMs: number = 0) { return await new Promise(r => setTimeout(r, periodMs)) } /** * Retries the execution of an async function */ // $FlowIgnore export async function asyncRetry(asyncFunc: (triesExecuted?: ?number) => Promise, options: AsyncRetryOptions = {}): Promise { // set defaults options.maxRetries = (typeof options.maxRetries === 'undefined') ? 2 : options.maxRetries if (typeof options.maxRetries !== 'number') throw new Error(`options.maxRetries is of an invalid type`) let triesExecuted = 0 let nextBackoffMs = options.expoBackoffMs /* eslint-disable no-constant-condition */ while (true) { try { triesExecuted++ return await asyncFunc(triesExecuted) } catch (err) { if (options.asyncErrorHandler) { await options.asyncErrorHandler(err, triesExecuted) } if (triesExecuted > options.maxRetries) { if (typeof err === 'object') { err._triesExecuted = triesExecuted } throw err } if (nextBackoffMs) { await sleep(nextBackoffMs) nextBackoffMs *= 1.5 } } } }