Skip to content

Instantly share code, notes, and snippets.

@sagirk
Last active May 6, 2021 06:43
Show Gist options
  • Save sagirk/fea044fe5d80edecb69a25e3210bfabe to your computer and use it in GitHub Desktop.
Save sagirk/fea044fe5d80edecb69a25e3210bfabe to your computer and use it in GitHub Desktop.
Poll without hammering the server – use the exponential backoff algorithm to space out retries
/**
* A configuration object for `pollWithExponentialBackoff`
*/
interface IPollOptions {
/**
* A callback that will be run on every poll attempt
*/
executor: () => Promise<any>
/**
* A callback that will be run after each poll attempt to check if further
* attempts can be cancelled
*/
watcher: (result: any) => boolean
/**
* The interval base
*/
intervalBase: number
/**
* The interval multiplier
*/
intervalMultiplier: number
/**
* The maximum number of poll attempts
*/
maxAttempts: number
}
/**
* Poll without hammering the server – use the exponential backoff algorithm to
* space out retries
*
* @see {@link https://en.wikipedia.org/wiki/Exponential_backoff | Wikipedia}
* to learn more about the exponential backoff algorithm
*
* @example Polling an image server to check if a batch upload is processed
* ```
* let jobLogDetails = null
* try {
* jobLogDetails = await pollWithExponentialBackoff({
* executor: async () => await client.getJobLogDetails({companyHandle, jobName}),
* watcher: (jobLogArray) => jobLogArray?.items[0]?.endDate,
* intervalBase: 500,
* intervalMultiplier: 2,
* maxAttempts: 10,
* })
* } catch (error) {
* console.log(`Error fetching job log details: ${error}`)
* }
* ```
*
* @param options - A configuration object
* @returns A promise that resolves to the poll result (i.e., the response
* received from the server)
*/
async function pollWithExponentialBackoff(options: IPollOptions): Promise<any> {
const {
executor,
watcher,
intervalBase,
intervalMultiplier,
maxAttempts,
} = options
let attemptsSoFar = 0
const runPoll = async (
resolve: (value: any) => any,
reject: (reason: Error) => any
) => {
const result = await executor()
attemptsSoFar += 1
if (watcher(result)) {
return resolve(result)
}
if (maxAttempts && attemptsSoFar === maxAttempts) {
return reject(new Error('Exceeded max attempts'))
}
setTimeout(
runPoll,
intervalBase * intervalMultiplier ** attemptsSoFar,
resolve,
reject
)
}
return new Promise(runPoll)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment