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);
}
}
@antony
Copy link

antony commented Apr 29, 2019

In case it's useful to anyone, here is a published package with all fixes mentioned above included, and tests added.

https://www.npmjs.com/package/@beyonk/promise-retry

@SahRckr
Copy link

SahRckr commented Jun 19, 2019

@eliseumds

your recursive call parameters seems incorrect,

function retry(fn, retriesLeft = 5, interval = 1000) {
// ...code...
retry(fn, retriesLeft - 1, interval).then(resolve, reject);
// retry(fn, interval, retriesLeft - 1).then(resolve, reject);

@givingwu
Copy link

Finally version:

export function retryPromise(fn, retriesLeft = 3, interval = 200) {
  return new Promise((resolve, reject) => {
    return fn()
      .then(resolve)
      .catch((error) => {
         if (retriesLeft === 1) {
            // reject('maximum retries exceeded');
            reject(error)
            return
          }

        setTimeout(() => {
          console.log('retriesLeft: ', retriesLeft)
          // Passing on "reject" is the important part
          retry(fn, retriesLeft - 1, interval).then(resolve, reject)
        }, interval)
      })
  })
}

@CristianBonilla
Copy link

const wait = interval => new Promise(resolve => setTimeout(resolve, interval));
async function retryPromise(fn, retriesLeft = 3, interval = 200) {
  try {
    return await fn;
  } catch (error) {
    await wait(interval);
    if (retriesLeft === 0) {
      throw new Error(error);
    }
    console.log('retriesLeft: ', retriesLeft);

    return await retryPromise(fn, --retriesLeft, interval);
  };
}
retryPromise(Promise.reject('Maximum retries exceeded!'), 5, 1000);

@DevJett
Copy link

DevJett commented Apr 12, 2021

@cristianb
You're missing () in here return await fn; we need it to run the imported component, it should be return await fn();

So final code is


const wait = interval => new Promise(resolve => setTimeout(resolve, interval));
export async function retry(fn, retriesLeft = 3, interval = 200) {
    try {
        return await fn();
    } catch (error) {
        await wait(interval);
        if (retriesLeft === 0) {
            throw new Error(error);
        }
        return await retry(fn, --retriesLeft, interval);
    }
}

@bablukpik
Copy link

bablukpik commented Oct 18, 2021

In case we want to pass parameters with the retry function like this

const wait = interval => new Promise(resolve => setTimeout(resolve, interval));
async function retry(
  fn,
  args = [],
  retriesLeft = 3,
  interval = 300,
) {
  try {
    return await fn(...args);
  } catch (error) {
    await wait(interval);
    if (retriesLeft === 0) {
      throw new Error(error);
    }
    return retry(fn, args, --retriesLeft, interval);
  }
}

// uses
const promiseFn1 = function(val) {
  return new Promise( (resolutionFunc) => {
    resolutionFunc(val);
  });
}
await retry(promiseFn1, [88, 2]);

For example:

async function myFun () {
  const wait = interval => new Promise(resolve => setTimeout(resolve, interval));
  async function retry(
    fn,
    args=[],
    retriesLeft = 3,
    interval = 300,
  ) {
    try {
      return await fn(...args);
    } catch (error) {
      await wait(interval);
      if (retriesLeft === 0) {
        throw new Error(error);
      }
      return retry(fn, args, --retriesLeft, interval);
    }
  }

  // uses
  const promiseFn1 = function(val) {
    return new Promise((resolve, reject) => {
      console.log('called');
      if (val) return resolve(val)
      return reject('This is reject val');
    });
  }
  const result = await retry(promiseFn1);
  console.log('result', result);
}

myFun();

@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);
})

@vitaly-t
Copy link

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