flighty
Version:
Fetch wrapper. Polyfills optional. Aborts, retries, intercepts all in 5kb
81 lines (66 loc) • 2.15 kB
JavaScript
export const retryDelayFn = delay => new Promise(res => setTimeout(() => res(), delay));
const checkFn = (fn, err) => {
if (typeof fn !== 'function') {
throw new Error(err);
}
};
export const asyncRetry = async (asyncFnToRetry, { retries = 0, retryDelay = 1000, retryFn }) => {
checkFn(asyncFnToRetry, 'retry function is not a function');
if (typeof retries !== 'number' || !Number.isInteger(retries) || retries < 0) {
throw new Error('retries must be a number greater than or equal to 0');
}
if (retryDelay && (typeof retryDelay !== 'number' || !Number.isFinite(retryDelay))) {
throw new Error('retryDelay must be a number (milliseconds)');
}
if (retryFn && typeof retryFn !== 'function') {
throw new Error('retryFn must be callable');
}
const localRetryFn = async (...args) => (retryFn ? retryFn(...args) : retryDelayFn(retryDelay));
let count = -1;
const wrap = async (remaining) => {
try {
count++;
return await asyncFnToRetry(count);
} catch (err) {
if (!remaining) {
throw err;
}
await localRetryFn(count + 1, err);
return wrap(remaining - 1);
}
};
const res = await wrap(retries);
return { count, res };
};
export const fetchRetry = async (fetchToRetry, {
retries, retryDelay, retryFn, retryOn = [], signal,
}) => {
checkFn(fetchToRetry, 'retry function is not a function');
if (retryOn && !Array.isArray(retryOn)) {
throw new Error('retryOn must be an array of response statii');
}
if (signal != null && typeof signal.aborted !== 'boolean') {
throw new Error('signal must have boolean "aborted" property');
}
return asyncRetry(
async (retryCount) => {
const res = await fetchToRetry();
if (retryOn.indexOf(res.status) === -1 || retries === retryCount) {
return res;
}
throw new Error(res);
},
{
retries,
retryFn: async (count, err) => {
if (signal && signal.aborted) {
throw err;
}
if (retryFn) {
return retryFn(count, err);
}
return retryDelayFn(retryDelay);
},
},
);
};