Reputation: 2517
I have seen a number of questions around retrying Promises, however what I'm looking to do is slightly different in that I'd like to manage the retrying/rejecting of promises conditionally until the max retries have been reached.
To give a simple example, imagine we wrap a promise around an XMLHttpRequest
. When the request loads with a status of...
200
: resolve the Promise299
: retry immediately399
: reject immediately499
: fetch something from server, then retryNotice that there is scope here for asynchronous behavior to be executed before retries.
The solution I have been looking into involves two Promises.
Bringing this back to the example I mentioned...
XmlHttpRequest
, resolving on status 200
and rejecting otherwise.I think I'm going in the right direction with this, but can't seem to get a concrete solution in place. I'm looking to create a generic wrapper for this kind of 'conditionally retrying promise.'
Edit:
Here is a solution in progress:
async function tryAtMost(maxAttempts, asyncCall, handleError)
{
for (let i = 0; i < maxAttempts; i++)
{
try
{
return await asyncCall();
}
catch (error)
{
const nextAction = await handleError(error); // await some async request (if available) before proceeding
const actionError = new Error(nextAction.error);
switch (nextAction.type)
{
case ACTIONS.ABORT:
throw actionError;
case ACTIONS.RETRY:
if (i === maxAttempts - 1) { throw actionError; }
else { continue; }
}
}
}
}
Upvotes: 4
Views: 837
Reputation: 22949
Based off your comment:
I'm looking to create a generic wrapper for this kind of "conditionally" retrying promise.
Here's a more generalised wrapper for this:
Promise
.// Class Retryable
class Retryable {
constructor({ promise, maxAttempts = 1, attemptRetry }) {
this.promise = promise
this.maxAttempts = maxAttempts
this.attemptRetry = attemptRetry
this.attempts = 0
}
generateTry() {
console.info('generating request')
return this.promise().catch(err => {
if (++this.attempts === this.maxAttempts) throw err
return this.attemptRetry(err, () => this.generateTry() , () => {
throw err
})
})
}
}
// Usage
const retryable = new Retryable({
maxAttempts: 4,
promise: () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject({ status: 500 })
// If you `resolve` here instead you will trigger `.then()`
}, 200)
})
},
attemptRetry: function(err, yes, no) {
switch (err.status) {
case 500:
return yes()
break;
default:
return no()
}
}
})
retryable.generateTry().then(result => {
console.log(result)
}).catch(err => {
console.error(err)
})
Upvotes: 1
Reputation: 22949
I would simply create a Class that returns an async
function (which returns a Promise
).
Class
instance keeps track of the attempts
.async
function attempts to fetch something x number of times, equal to the number of maxAttempts
.maxAttempts
.const rp = require('request-promise-native')
class RetryableFetch {
constructor({ url, maxAttempts = 3 }) {
this.url = url
this.maxAttempts = maxAttempts
this.attempts = 0
return this.generateRequest()
}
async generateRequest() {
for (let i = 0; i < this.maxAttempts; i++) {
try {
return await rp(this.url)
} catch(err) {
switch (err.statusCode) {
// Add more cases here as you see fit.
case 399:
throw err
break;
default:
if (++this.attempts === this.maxAttempts) throw err
}
}
}
}
}
new RetryableFetch({
url: 'https://www.google.com'
})
.then(result => {
console.log(result)
})
.catch(err => {
console.error(err)
})
You can of course substitute rp
with Fetch if you want this to work in the browser since both use a Promise-based API.
Upvotes: 1
Reputation: 161457
There are a few ways to do this, as the other post shows. Personally I find the usage of class unnecessary. I'd approach it using something like
async function fetchWithRetries(theURL, remainingRetries = 5) {
const response = await fetch(theURL);
switch (response.status) {
case 200:
return await response.json(); // or whatever you need
case 299:
if (remainingRetries === 0) {
throw new Error();
}
return await fetchWithRetries(theURL, remainingRetries - 1);
case 399:
throw new Error();
case 499:
if (remainingRetries === 0) {
throw new Error();
}
const otherData = await fetchOtherData();
return await fetchWithRetries(theURL, remainingRetries - 1);
default:
// TODO: You didn't specify other codes?
}
}
Upvotes: 2