I thought I might as well throw my hat in the ring, using ES6 Promises...
function until_success(executor){
var before_retry = undefined;
var outer_executor = function(succeed, reject){
var rejection_handler = function(err){
if(before_retry){
try {
var pre_retry_result = before_retry(err);
if(pre_retry_result)
return succeed(pre_retry_result);
} catch (pre_retry_error){
return reject(pre_retry_error);
}
}
return new Promise(executor).then(succeed, rejection_handler);
}
return new Promise(executor).then(succeed, rejection_handler);
}
var outer_promise = new Promise(outer_executor);
outer_promise.before_retry = function(func){
before_retry = func;
return outer_promise;
}
return outer_promise;
}
The executor
argument is the same as that passed to a Promise
constructor, but will be called repeatedly until it triggers the success callback. The before_retry
function allows for custom error handling on the failed attempts. If it returns a truthy value it will be considered a form of success and the "loop" will end, with that truthy as the result. If no before_retry
function is registered, or it returns a falsey value, then the loop will run for another iteration. The third option is that the before_retry
function throws an error itself. If this happens, then the "loop" will end, passing that error as an error.
Here is an example:
var counter = 0;
function task(succ, reject){
setTimeout(function(){
if(++counter < 5)
reject(counter + " is too small!!");
else
succ(counter + " is just right");
}, 500); // simulated async task
}
until_success(task)
.before_retry(function(err){
console.log("failed attempt: " + err);
// Option 0: return falsey value and move on to next attempt
// return
// Option 1: uncomment to get early success..
//if(err === "3 is too small!!")
// return "3 is sort of ok";
// Option 2: uncomment to get complete failure..
//if(err === "3 is too small!!")
// throw "3rd time, very unlucky";
}).then(function(val){
console.log("finally, success: " + val);
}).catch(function(err){
console.log("it didn't end well: " + err);
})
Output for option 0:
failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
failed attempt: 4 is too small!!
finally, success: 5 is just right
Output for option 1:
failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
finally, success: 3 is sort of ok
Output for option 2:
failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
it didn't end well: 3rd time, very unlucky