Handling Retries and Back-off Attempts with JavaScript Promises

Promises are an invaluable abstraction around ’eventual’ results within asynchronous operations. I recently needed to retry a Promise-based action in the event of a failure. It turned out to be very easy to implement such a process using simple recursive constructs.

Initially, I only required the ability to retry a desired number of times before eventually failing if still unsuccessful. You can see how easy it was to describe this problem in Promise form within the function below.

const retry = (retries, fn) =>
  fn().catch(err =>
    retries > 1 ? retry(retries - 1, fn) : Promise.reject(err)
  );

However, what I eventually required was the ability to ‘back off’ and provide an increasing grace period between operation attempts. Again, it was very easy to describe this within a Promise, as shown below.

const pause = duration => new Promise(res => setTimeout(res, duration));

const backoff = (retries, fn, delay = 500) =>
  fn().catch(err =>
    retries > 1
      ? pause(delay).then(() => backoff(retries - 1, fn, delay * 2))
      : Promise.reject(err)
  );

As you can see, both implementations use a recursive structure with decrementing retries to hit the base case. What I find so impressive about the Promise abstraction is how easy it is to codify complex problems such as this with minimal code.