Promises: Repeat operation until it succeeds?
All the answers here are really complicated in my opinion. Kos has the right idea but you can shorten the code by writing more idiomatic promise code:
function retry(operation, delay) {
return operation().catch(function(reason) {
return Q.delay(delay).then(retry.bind(null, operation, delay * 2));
});
}
And with comments:
function retry(operation, delay) {
return operation(). // run the operation
catch(function(reason) { // if it fails
return Q.delay(delay). // delay
// retry with more time
then(retry.bind(null, operation, delay * 2));
});
}
If you want to time it out after a certain time (let's say 10 seconds , you can simply do:
var promise = retry(operation, 1000).timeout(10000);
That functionality is built right into Q, no need to reinvent it :)
Here's an example of how I'd approach this, with some helper functions. Note, the 'maxTimeout' is the more complicated part because you have to race two states.
// Helper delay function to wait a specific amount of time.
function delay(time){
return new Promise(function(resolve){
setTimeout(resolve, time);
});
}
// A function to just keep retrying forever.
function runFunctionWithRetries(func, initialTimeout, increment){
return func().catch(function(err){
return delay(initialTimeout).then(function(){
return runFunctionWithRetries(
func, initialTimeout + increment, increment);
});
});
}
// Helper to retry a function, with incrementing and a max timeout.
function runFunctionWithRetriesAndMaxTimeout(
func, initialTimeout, increment, maxTimeout){
var overallTimeout = delay(maxTimeout).then(function(){
// Reset the function so that it will succeed and no
// longer keep retrying.
func = function(){ return Promise.resolve() };
throw new Error('Function hit the maximum timeout');
});
// Keep trying to execute 'func' forever.
var operation = runFunctionWithRetries(function(){
return func();
}, initialTimeout, increment);
// Wait for either the retries to succeed, or the timeout to be hit.
return Promise.race([operation, overallTimeout]);
}
Then to use these helpers, you'd do something like this:
// Your function that creates a promise for your task.
function doSomething(){
return new Promise(...);
}
runFunctionWithRetriesAndMaxTimeout(function(){
return doSomething();
}, 1000 /* start at 1s per try */, 500 /* inc by .5s */, 30000 /* max 30s */);
I think you can't do it on promise level - a promise isn't an operation, but is just a value that's going to arrive in the future, so you can't define a function typed Promise -> Promise
that will achieve it.
You'd need to go one level down and define a function typed Operation -> Promise
where Operation is itself typed () -> Promise
. I assume the operation doesn't take any parameters - you can partially-apply them beforehand.
Here's a recursive approach that doubles the timeout on every run:
function RepeatUntilSuccess(operation, timeout) {
var deferred = Q.defer();
operation().then(function success(value) {
deferred.resolve(value);
}, function error(reason) {
Q.delay(timeout
.then(function() {
return RepeatUntilSuccess(operation, timeout*2);
}).done(function(value) {
deferred.resolve(value);
});
});
return deferred.promise;
}
Demo: http://jsfiddle.net/0dmzt53p/