6

I have a function say myMainFunction that is called from a client, that in turn calls mypromisified function.

Scenario: mypromisified function can fail intermittently and I need to call this function with a delay (at an exponential increase) until success or until max no of tries reached.

What I have so far

The following code illustrates my scenario and repeats itself until success, but it tries indefinitely and not until certain count is reached

// called once from the client
myMainFuntion();

function rejectDelay(delay, reason) {
   // call main function at a delayed interval until success 
   // but would want to call this only a limited no of times
    setTimeout(() => {
      myMainFuntion(); // calling main function again here but with a delay
    }, delay);
}


function myMainFuntion() {
  var delay = 100;
  var tries = 3;
  tryAsync().catch(rejectDelay.bind(null, delay));
}

function tryAsync() {
  return new Promise(function(resolve, reject) {
    var rand = Math.random();
    console.log(rand);
    if (rand < 0.8) {
      reject(rand);
    } else {
      resolve();
    }
  });

}

while loop inside the rejectDelay would certainly not work as the counter would increment even before the actual function inside setInterval is executed, so am unsure as to how to go about this? so...

I tried promisifying the setInterval something like this knowing it will fail :( as it doesnt decrement the counter, but not sure how to get it right either .

function rejectDelay(delay, maximumTries, reason) {
  return new Promise(function (resolve, reject) {
    console.log(tries + ' remaining');
    if (--maximumTries > 0) {
      setTimeout(function() {
        foo();
      }, 500);
    } 
  });
}
function myMainFunction() {
  var delay = 100;
  var maximumTries = 3;
  tryAsync().catch(rejectDelay.bind(null, delay, maximumTries));
}
Jaya
  • 3,721
  • 4
  • 32
  • 48
  • Related answers: [Promise retry design patterns](http://stackoverflow.com/questions/38213668/promise-retry-design-patterns/38214203#38214203) and [Retry a promise step](http://stackoverflow.com/questions/37993365/retry-a-promise-step/37995874#37995874) – jfriend00 Feb 24 '17 at 03:38

2 Answers2

16

Using a couple of helper functions I've used a lot, this becomes very easy

The "helpers"

Promise.wait = (time) => new Promise(resolve => setTimeout(resolve, time || 0));
Promise.retry = (cont, fn, delay) => fn().catch(err => cont > 0 ? Promise.wait(delay).then(() => Promise.retry(cont - 1, fn, delay)) : Promise.reject('failed'));

The code:

function myMainFuntion() {
      var delay = 100;
      var tries = 3;
      Promise.retry(tries, tryAsync, delay);
}

ES5 versions of the helpers

Promise.wait = function (time) {
    return new Promise(function (resolve) {
        return setTimeout(resolve, time || 0);
    });
};
Promise.retry = function (cont, fn, delay) {
    return fn().catch(function (err) {
        return cont > 0 ? Promise.wait(delay).then(function () {
            return Promise.retry(cont - 1, fn, delay);
        }) : Promise.reject('failed');
    });
};
Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • thanks a ton for a terse solution :) . I understand the recursive call made on `retry`, but unable to get my head around the wait function -> I see that you are returning a new Promise, but not explicilty resolving or rejecting the same? - which in my head means `.then` on `Promise.wait()` would never be called? Do you mind explaning it to me a bit more? thank you in advance PS: I know it is working from the rip off :) that i made here to https://jsfiddle.net/jayas_godblessall/j50u8br9/ - but as mentioned - not working in my head :( – Jaya Feb 24 '17 at 04:11
  • 1
    the first argument to `setTimeout` is a function to run ... it's set to `resolve` ... therefore resolve gets called after `time` ms – Jaromanda X Feb 24 '17 at 04:18
  • Nice recursive definition of Promise.retry! Note that the function "fn" will actually be called "cont"+1 times here, unless you compare cont to 1. – David Ha Jul 12 '17 at 07:54
  • Depends on how you think of retry I guess – Jaromanda X Jul 12 '17 at 08:58
0

A slightly different approach that uses "asynchronous recursion" to call a nested function within a function that returns a promise:

function retry( func, maxTries, delay) {
    var reTry = 0;
    return new Promise( function(resolve, reject) {
        function callFunc() {
          try
          {
              func().then(resolve, function( reason) {
                  if( ++reTry >= maxTries) {
                      reject( reason);
                  }
                  else {
                      setTimeout( callFunc,
                         typeof delay=="function" ? delay( retry) : delay );
                  }
              });
          }
          catch(e) {
              reject(e);
          }
        }
        callFunc();      
    });
}

//  ******* run snippet to test ********


var retryCount = 0;
function getDelay( n) {
//    return 100 * n*n + 500; // for example
      ++ retryCount;
      return 100;  // for testing

}

function testFunc() {
    return Math.random() < 0.8 ? Promise.reject("too many tries") : Promise.resolve( "success");
}

retry( testFunc, 5, getDelay).then(
 function(data) { console.log("data: %s, retryCount %s", data, retryCount);},
 function(reason){console.log("reason: %s, retryCount %s", reason, retryCount);}
)
traktor
  • 17,588
  • 4
  • 32
  • 53