This question was asked in Google's frontend interview and Impact Analytics SSE – Frontend interview.
The problem statement reads as Implement a function in JavaScript that retries promises N number of times with a delay between each call.
Input:
retry(asyncFn, retries = 3, delay = 50, finalError = 'Failed');
Output:
... attempt 1 -> failed
... attempt 2 -> retry after 50ms -> failed
... attempt 3 -> retry after 50ms -> failed
... Failed.
We have to create a retry function that Keeps on retrying until the promise resolves with delay and max retries.
We will see two code examples one with thenable promise and the second with async…await.
We can create a delay function by creating a new promise and resolve it after a given time using setTimeout.
//delay func
const wait = ms => new Promise((resolve) => {
setTimeout(() => resolve(), ms);
});
To retry the promise we have to call the same function recursively with reduced max tries, if the promise fails that is in the catch block. Check if there is a number of tries left then recursively call the same function or else reject with the final error.
const retryWithDelay = (
operation, retries = 3,
delay = 50, finalErr = 'Retry failed') => new Promise((resolve, reject) => {
return operation()
.then(resolve)
.catch((reason) => {
//if retries are left
if (retries > 0) {
//delay the next call
return wait(delay)
//recursively call the same function to retry with max retries - 1
.then(retryWithDelay.bind(null, operation, retries - 1, delay, finalErr))
.then(resolve)
.catch(reject);
}
// throw final error
return reject(finalErr);
});
});
To test we can create a test function that keeps throwing errors if called less than 5 times. So if we will call it 6 or more times, it will pass.
Input:
// Test function
const getTestFunc = () => {
let callCounter = 0;
return async () => {
callCounter += 1;
// if called less than 5 times
// throw error
if (callCounter < 5) {
throw new Error('Not yet');
}
}
}
// Test the code
const test = async () => {
await retryWithDelay(getTestFunc(), 10);
console.log('success');
await retryWithDelay(getTestFunc(), 3);
console.log('will fail before getting here');
}
// Print the result
test().catch(console.error);
Output:
"success" // 1st test
"Retry failed" //2nd test
We can implement the same logic using async-await. When using async-await, we need to wrap the code inside try..catch block to handle the error, thus in the catch block, we can check if the max retries are still left then recursively call the same function or else throw the final error.
const retryWithDelay = async (
fn, retries = 3, interval = 50,
finalErr = 'Retry failed'
) => {
try {
// try
await fn();
} catch (err) {
// if no retries left
// throw error
if (retries <= 0) {
return Promise.reject(finalErr);
}
//delay the next call
await wait(interval);
//recursively call the same func
return retryWithDelay(fn, (retries - 1), interval, finalErr);
}
}