77 lines
2.5 KiB
JavaScript
77 lines
2.5 KiB
JavaScript
// [start-readme]
|
|
//
|
|
// Return a function that you can use to run any code within and if it
|
|
// throws you get a chance to say whether to sleep + retry.
|
|
// Example:
|
|
//
|
|
// async function mainFunction() {
|
|
// if (Math.random() > 0.9) throw new Error('too large')
|
|
// return 'OK'
|
|
// }
|
|
//
|
|
// const errorTest = (err) => err instanceof Error && err.message.includes('too large')
|
|
// const config = { // all optional
|
|
// attempts: 3,
|
|
// sleepTime: 800,
|
|
// onError: (err, attempts) => console.warn(`Failed ${attempts} attempts`)
|
|
// }
|
|
// const ok = await retry(errorTest, mainFunction, config)
|
|
//
|
|
// Note that, by default, the sleep time is "exponential" by a factor of
|
|
// 1.5. So the first sleep will, in the above example,
|
|
// be 800ms. Then 1,200ms, Then 1,800ms. etc.
|
|
//
|
|
// [end-readme]
|
|
|
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
|
|
export async function retryOnErrorTest(
|
|
errorTest,
|
|
callback,
|
|
{
|
|
attempts = 4,
|
|
sleepTime = 1000,
|
|
exponential = 1.5,
|
|
jitterPercent = 25,
|
|
onError = () => {},
|
|
} = {},
|
|
) {
|
|
while (true) {
|
|
try {
|
|
return await callback()
|
|
} catch (error) {
|
|
if (error instanceof Error && attempts > 0 && errorTest(error)) {
|
|
if (onError) onError(error, attempts, sleepTime)
|
|
attempts--
|
|
// The reason for the jitter is to avoid a thundering herd problem.
|
|
// Suppose two independent processes/threads start at the same time.
|
|
// They both fail, perhaps due to rate limiting. Now, if they both
|
|
// sleep for 30 seconds in the first retry attempt, it'll just
|
|
// clash again 30 seconds later. But if you add a bit of jitter, at
|
|
// the next attempt these independent processes/threads will now
|
|
// start at slightly different times.
|
|
|
|
// According to the Oxford English dictionary, they define "jitter" as:
|
|
//
|
|
// slight irregular movement, variation, or unsteadiness,
|
|
// especially in an electrical signal or electronic device.
|
|
//
|
|
await sleep(addJitter(sleepTime, jitterPercent))
|
|
if (exponential) {
|
|
sleepTime *= 2
|
|
}
|
|
} else {
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function addJitter(num, percent) {
|
|
// Return the number plus between 0 and $percent of that number.
|
|
// For example, for 1,000 with a 20% jitter you might get 1133.4
|
|
// because you start with 1,000 and 13.4% is a random number between
|
|
// 0 and 20%.
|
|
return num + Math.random() * percent * 0.01 * num
|
|
}
|