JavaScript Promise-based backoff retry for async calls

2022-11-23

In the modern cloud-based, multi-service-tiered world, we often must call an API to get or to create some data. From time to time, the service responsible for the exposed API is failing to properly handle the request due to various reasons: heavy load spikes, degraded performance, deployment timeout, etc...

One way to solve this issue is to give some time to the service to get back on track and retry our request before giving up. We could set a limit of retries and exponentially delaying their execution.

Let's see how a simple solution could look like:

const callAPIWrapper = (retryCount = 0, maxRetries = MAX_RETRIES) => { return retryCount > maxRetries ? runFallbacks() // (1) : callAPI() // (2) .catch(() => delay(retryCount) .then(() => callAPIWrapper(retryCount + 1, maxRetries))); };

In the example, callAPIWrapper is our *main* function, which will be called recursively, if the API fails. Inside, we have the check how many times we have called the function, and based on the answer we either give up and calling runFallbacks(1) or we calm down a bit and try again (2).

The runFallbacks is optional to implement, a good place where we can notify that the API call was not successful at all, i.e. sending alert to Slack, sending Email, notifying our monitoring system, etc...

The second part, (2), is where the real fun starts. We try to call our API, callAPI(), and if if fails, in the `catch` clause we delay the execution:

const delay = (retryCount) => { return new Promise(resolve => setTimeout(resolve, 1000 * retryCount)); };

We return a promise, which is resolved after n-seconds, where n is based on the `rertyCount`. Here, we can define the interval based on our needs. Then we call the callAPIWrapper again.

In this short guide, I've presented a simple, yet effective way of retrying an async API call, that could help you design more robust services.

If you have any comments, don't hesitate to drop me a message at ping@viktorpenkov.com.