Last active
May 6, 2021 06:43
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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