Have you tried turning it on and off again? The web is a weird place and calls might not always succeed in the right manner. A retry with an exponential back-off mechanism helps your code to be more resilient when it connects to services outside of your control. While there are many packages that can help in this area, it pretty easy to add some utility methods to your project. In this article I'll show how you can create a general-purpose exponential back-off and retry mechanism using TypeScript and Node.js.
Wait for it...
Let's create a delay function first. It used the setTimeout
mechanism:
export function delay(ms: number): Promise<void> {
return new Promise<void>(resolve => {
setTimeout(resolve, ms)
})
}
Retry and exponential back-off
The exponential back-off is important, because it prevents your code from hammering the other system if your call was unsuccessful. By using 2^x
we get an exponential back-off sequence: 1, 2, 4, 8, 16
.
export async function retryWithBackoff<T>(fn: () => Promise<T>, retries = 5, backoffInMs = 100): Promise<T> {
let x = 0
while (true) {
try {
return await fn()
} catch (ex) {
if (x == retries) {
throw ex
}
// exponential back-off
let msToSleep = Math.pow(2, x) * backoffInMs
// add some extra random ms to make sure we're not
// retrying on exactly the same interval
let rndSleep = Math.random() * 100
await delay(msToSleep + rndSleep)
x += 1
}
}
}
Example
Let's test our code with the following example:
async function test() {
console.time("mark")
try {
await retryWithBackoff(() => {
console.timeEnd("mark")
console.time("mark")
throw "auo"
})
} catch (ex) {
console.log("All is broken!")
console.timeEnd("mark")
}
}
test()
This outputs:
mark: 0.495ms
mark: 128.712ms
mark: 268.073ms
mark: 478.778ms
mark: 834.063ms
mark: 1632.867ms
All is broken!
mark: 0.285ms
That's all you need.