86 lines
2 KiB
JavaScript
86 lines
2 KiB
JavaScript
|
'use strict';
|
||
|
const retry = require('retry');
|
||
|
|
||
|
const networkErrorMsgs = [
|
||
|
'Failed to fetch', // Chrome
|
||
|
'NetworkError when attempting to fetch resource.', // Firefox
|
||
|
'The Internet connection appears to be offline.', // Safari
|
||
|
'Network request failed' // `cross-fetch`
|
||
|
];
|
||
|
|
||
|
class AbortError extends Error {
|
||
|
constructor(message) {
|
||
|
super();
|
||
|
|
||
|
if (message instanceof Error) {
|
||
|
this.originalError = message;
|
||
|
({message} = message);
|
||
|
} else {
|
||
|
this.originalError = new Error(message);
|
||
|
this.originalError.stack = this.stack;
|
||
|
}
|
||
|
|
||
|
this.name = 'AbortError';
|
||
|
this.message = message;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const decorateErrorWithCounts = (error, attemptNumber, options) => {
|
||
|
// Minus 1 from attemptNumber because the first attempt does not count as a retry
|
||
|
const retriesLeft = options.retries - (attemptNumber - 1);
|
||
|
|
||
|
error.attemptNumber = attemptNumber;
|
||
|
error.retriesLeft = retriesLeft;
|
||
|
return error;
|
||
|
};
|
||
|
|
||
|
const isNetworkError = errorMessage => networkErrorMsgs.includes(errorMessage);
|
||
|
|
||
|
const pRetry = (input, options) => new Promise((resolve, reject) => {
|
||
|
options = {
|
||
|
onFailedAttempt: () => {},
|
||
|
retries: 10,
|
||
|
...options
|
||
|
};
|
||
|
|
||
|
const operation = retry.operation(options);
|
||
|
|
||
|
operation.attempt(async attemptNumber => {
|
||
|
try {
|
||
|
resolve(await input(attemptNumber));
|
||
|
} catch (error) {
|
||
|
if (!(error instanceof Error)) {
|
||
|
reject(new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (error instanceof AbortError) {
|
||
|
operation.stop();
|
||
|
reject(error.originalError);
|
||
|
} else if (error instanceof TypeError && !isNetworkError(error.message)) {
|
||
|
operation.stop();
|
||
|
reject(error);
|
||
|
} else {
|
||
|
decorateErrorWithCounts(error, attemptNumber, options);
|
||
|
|
||
|
try {
|
||
|
await options.onFailedAttempt(error);
|
||
|
} catch (error) {
|
||
|
reject(error);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!operation.retry(error)) {
|
||
|
reject(operation.mainError());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
module.exports = pRetry;
|
||
|
// TODO: remove this in the next major version
|
||
|
module.exports.default = pRetry;
|
||
|
|
||
|
module.exports.AbortError = AbortError;
|