Skip to content

Instantly share code, notes, and snippets.

@briancavalier
Created February 24, 2011 18:35
Show Gist options
  • Save briancavalier/842626 to your computer and use it in GitHub Desktop.
Save briancavalier/842626 to your computer and use it in GitHub Desktop.
A few general patterns for retries using promises
function keepTrying(otherArgs, promise) {
promise = promise||new Promise();
// try doing the important thing
if(success) {
promise.resolve(result);
} else {
setTimeout(function() {
keepTrying(otherArgs, promise);
}, retryInterval);
}
}
function keepTrying(otherArgs, retryInterval, promise) {
promise = promise||new Promise();
// try doing the important thing
if(success) {
promise.resolve(result);
} else {
setTimeout(function() {
// Try again with incremental backoff, 2 can be
// anything you want. You could even pass it in.
// Cap max retry interval, which probably makes sense
// in most cases.
keepTrying(otherArgs, Math.min(maxRetryInterval, retryInterval * 2), promise);
}, retryInterval);
}
}
function tryAtMost(otherArgs, maxRetries, promise) {
promise = promise||new Promise();
// try doing the important thing
if(success) {
promise.resolve(result);
} else if (maxRetries > 0) {
// Try again if we haven't reached maxRetries yet
setTimeout(function() {
tryAtMost(otherArgs, maxRetries - 1, promise);
}, retryInterval);
} else {
promise.reject(error);
}
}
@MarkellRichards
Copy link

@bablukpik any idea how this would work with Batch operations? Like using promise.All or promise.AllSettled?

@basedwon
Copy link

basedwon commented May 25, 2022

my try:

const retry = (fn, delay = 1000, maxTries = 5) => {
  let timer
  const attempt = remain => {
    clearTimeout(timer)
    return new Promise((resolve, reject) => {
      timer = setTimeout(() => {
        if (remain <= 0)
          return reject(new Error(`Failed after ${maxTries} attempts`))
        resolve(attempt(remain - 1))
      }, delay)
      Promise.resolve(fn())
        .then(res => {
          clearTimeout(timer)
          resolve(res)
        })
        .catch(error => {
          clearTimeout(timer)
          if (remain <= 0)
            return reject(new Error(`Failed after ${maxTries} attempts - Error: ${error.toString()}`))
          resolve(attempt(remain - 1))
        })
    })
  }
  return attempt(maxTries)
}

@JoopAue
Copy link

JoopAue commented Jul 13, 2022

Based on previous examples, I have also added an internal multiplication function that based on the last interval sets a new internal.
For instance with doubling intervals and 3 retries.

  • First retry after 1000 ms
  • Second retry after 2000 ms
  • Third retry after 4000 ms

Typescript example:

export const retry = (fn: Function, retriesLeft = 5, interval = 1000, intervalMultiplier: (interval: number) => number = i => i) => new Promise((resolve, reject) => {
    console.log(`Retries left: ${retriesLeft} - Next retry interval: ${interval}`);
    fn()
        .then(resolve)
        .catch((error: unknown) => {
            if (retriesLeft === 0) {
                // reject('maximum retries exceeded');
                reject(error);
                return;
            }
            setTimeout(() => {
                // Passing on "reject" is the important part
                retry(fn, retriesLeft - 1, intervalMultiplier(interval), intervalMultiplier).then(resolve, reject);
            }, interval);
        });
});

// example function
const chanceOfFailing = () : Promise<string> => {
    return new Promise<string>((resolve, reject) => {
        if(Math.random() < 0.1) {
            resolve("success");
        } else {
            reject("fail");
        }
    });
}

// example usage
retry(chanceOfFailing, 3, 1000, i => i*2).then((result) => {
    console.log(result);
}).catch((error) => {
    console.log(error);
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment