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

Hey thanks for sharing, if that can help I'm using this (requires Q though)

var retryPromise = function(promise, args, maxTries, context) {
    context = context || null;
    return promise.apply(context, args)
    .then(function(d){
        return Q(d);
    },
    function(err){
        if (maxTries == -1) return Q.error(err);
        else return retryPromise(promise, args, maxTries - 1);
    }):
}

And you'd use it like that :

var promise = retryPromise(dataStore.loadMovies, [my, Arguments], 3,  dataStore)
.then(...)

@MichaelJCole
Copy link

Hey, what Promise library are you using? This doesn't appear to be EMCA 6 compatible: promise = promise||new Promise();

If you're looking for a Q solution, check this out: https://gist.github.com/kriskowal/593052

@jansoren
Copy link

👍

@samjross
Copy link

samjross commented May 9, 2018

@spacenick function(err){ if (maxTries == -1) return Q.error(err); swallows the error

@curran
Copy link

curran commented Jul 6, 2018

An alternative solution:

const retry = (fn, ms) => new Promise(resolve => { 
  fn()
    .then(resolve)
    .catch(() => {
      setTimeout(() => {
        console.log('retrying...');
        retry(fn, ms).then(resolve);
      }, ms);
    })
});

Related: puppeteer/puppeteer#2460

@r3wt
Copy link

r3wt commented Aug 9, 2018

@curran 's solution with max retries

const retry = (fn, ms=1000,maxRetries=5) => new Promise((resolve,reject) => { 
    var retries=0;
    fn()
    .then(resolve)
    .catch(() => {
        setTimeout(() => {
            console.log('retrying failed promise...');
            ++retries;
            if(retries==maxRetries) {
                return reject('maximum retries exceeded');
            }
            retry(fn, ms).then(resolve);
        }, ms);
    })
});

@alfafc
Copy link

alfafc commented Sep 7, 2018

@r3wt thanks!

@eliseumds
Copy link

eliseumds commented Sep 11, 2018

Thanks @r3wt!

I'd modify the code a bit because I wasn't being able to catch the max retries exception. Also, we don't really need a counter:

function retry(fn, retriesLeft = 5, interval = 1000) {
  return new Promise((resolve, reject) => {
    fn()
      .then(resolve)
      .catch((error) => {
        setTimeout(() => {
          if (retriesLeft === 1) {
            // reject('maximum retries exceeded');
            reject(error);
            return;
          }

          // Passing on "reject" is the important part
          retry(fn, interval, retriesLeft - 1).then(resolve, reject);
        }, interval);
      });
  });
}

@dinigo
Copy link

dinigo commented Nov 8, 2018

Heres an ES6, async/await version. Thank you guys! https://gitlab.com/snippets/1775781

@catchergeese
Copy link

@eliseumds If case you ever needed to debug it, you (I reckon accidentally) flipped arguments to retry there retry(fn, interval, retriesLeft - 1).then(resolve, reject);, it should be retry(fn, retriesLeft - 1, interval).then(resolve, reject);

@pettomartino
Copy link

@eliseumds

if (retriesLeft === 1) {
// reject('maximum retries exceeded');
  return reject(error);
}

I think that should be outside setTimeout

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

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