Created
September 5, 2023 14:28
-
-
Save hirenchauhan2/b6cf750a56fc0ae6bf76be7b3ff1e930 to your computer and use it in GitHub Desktop.
Retry a promise
This file contains 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
/** | |
* AbortError is a special error which is used for aborting the retry mechanism of the `retryPromise` function. | |
*/ | |
export class AbortError extends Error { | |
constructor(message: string) { | |
super(message); | |
} | |
} | |
/** | |
* Add waiting time in execution | |
* @param {number} time Number of milliseconds to wait | |
*/ | |
function waitFor(time: number) { | |
return new Promise((res) => { | |
setTimeout(res, time); | |
}); | |
} | |
interface RetryConfig { | |
retries: number; | |
onFailedAttempt: (error: ErrorTracker) => void; | |
}; | |
interface ErrorTracker { | |
attemptNumber: number; | |
retriesLeft: number; | |
originalError: any | |
} | |
/** | |
* Retry promise execution until it gives successful result under the given number of retries. | |
* | |
* @param input function to call and retry if any error occurs except for AbortError which will not allow to retry | |
* @param options config for retry options | |
* | |
*/ | |
export async function retryPromise(input: () => Promise<unknown>, options: RetryConfig) { | |
options = Object.assign({ retries: 2 }, options); | |
const retries = options.retries; | |
const errorTracker: ErrorTracker = { | |
attemptNumber: 0, | |
retriesLeft: retries, | |
originalError: null, | |
}; | |
for (let i = 0; i < retries; i++) { | |
try { | |
errorTracker.attemptNumber++; | |
const result = await input(); | |
return result; | |
} catch (error) { | |
errorTracker.originalError = error; | |
// exit immediately if the the error is for aborting the further retries. | |
if (error instanceof AbortError) { | |
throw error; | |
} | |
errorTracker.retriesLeft--; | |
if ( | |
options.onFailedAttempt && | |
typeof options.onFailedAttempt === 'function' | |
) { | |
options.onFailedAttempt(errorTracker); | |
} | |
// If the last attempt failed, then no need to add wait | |
if (errorTracker.retriesLeft > 0) { | |
await waitFor(errorTracker.attemptNumber * 1000); | |
} | |
} | |
} | |
throw errorTracker.originalError; | |
}; | |
/** | |
* Some function to get data from API or to do some async task | |
**/ | |
async function getRandomData(shouldFail = false, shouldAbort = false) { | |
const timeout = Math.floor(Math.random() * 2000); | |
console.log('random time to pretend a network call or some time consument task', timeout); | |
await waitFor(timeout); | |
if (shouldFail) { | |
if (shouldAbort) { | |
throw new AbortError('Abortttt....!'); | |
} | |
throw new Error('Some non-priority error here'); | |
} | |
return Promise.resolve('Yay!!'); | |
} | |
async function demo() { | |
try { | |
// should fail after 3 retries | |
const result = await retryPromise(() => getRandomData(true), { | |
retries: 3, | |
onFailedAttempt: (error) => { | |
console.log( | |
`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`, | |
error.originalError | |
); | |
}, | |
}); | |
console.log(result); | |
} catch (err) { | |
console.log('Caught the error', err); | |
} | |
} | |
demo(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment