The following solution addresses a few specific problems:
- how to break the (otherwise endless) loop
- how to access the result of a previously failed attempt
- how to incorporate your
params = modify(params)
The problem can be broken down into two sections:
- a repeater/promise runner (that itself returns a promise so we can hook
onSuccess
to it)
- a promise provider - a function that creates promises for the repeater
The repeater would look like this:
function repeatUntilSuccess(promiseProvider) {
return new Promise(function (resolve, reject) {
var counter = 0;
function run(failedResult) {
var p = promiseProvider(failedResult, counter++);
if (p instanceof Promise) {
p.then(resolve).catch(run);
} else {
reject(p);
}
}
run();
});
}
The "loop" happens in this line: p.then(resolve).catch(run);
. The repeater keeps calling the promise provider until the promise it returns resolves (in which case the repeater resolves) or until it no longer provides a promise (in which case the repeater rejects).
A promise provider can be any function(previousResult, counter)
that returns a promise (or not, if you wish to stop the loop).
var params = {/* ... */};
function nextRequest(previousResult, counter) {
if (counter >= 10) return "too many attempts";
if (previousResult) params = modify(params);
return apiRequest(params);
}
(This setup assumes that apiRequest()
returns a promise)
Now you can do this:
repeatUntilSuccess(nextRequest).then(onSuccess).catch(onError);
Since your question includes the side-task of wrapping an HTTP request in a promise:
function apiRequest(params) {
return new Promise(function (resolve, reject) {
return request(params).then(function (result) {
if (isGood(result)) {
resolve(result);
} else {
reject(result);
}
});
});
}
Open the browser console and run the following snippet to see it in action.
function repeatUntilSuccess(promiseProvider) {
return new Promise(function (resolve, reject) {
var counter = 0;
function run(failedResult) {
var p = promiseProvider(failedResult, counter++);
if (p instanceof Promise) {
p.then(resolve).catch(run);
} else {
reject(p);
}
}
run();
});
}
// mockup promise that resoves or rejects randomly after a timeout
function randomPromise(num){
return new Promise(function(resolve, reject){
setTimeout(function () {
var randomNum = Math.floor(Math.random() * num * 10);
if (randomNum < num) {
resolve(randomNum);
} else {
reject(randomNum);
}
}, 300);
});
}
// promise provider
function nextPromise(prev, i) {
if (prev) console.info("failed attempt #" + i + ": " + prev);
if (i >= 5) return "too many attempts:" + i;
return randomPromise(100);
}
// run it!
repeatUntilSuccess(nextPromise).then(function (result) {
console.log("success", result);
}).catch(function (result) {
console.log("failed", result);
});