index.js 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import retry from 'retry';
  2. import isNetworkError from 'is-network-error';
  3. export class AbortError extends Error {
  4. constructor(message) {
  5. super();
  6. if (message instanceof Error) {
  7. this.originalError = message;
  8. ({message} = message);
  9. } else {
  10. this.originalError = new Error(message);
  11. this.originalError.stack = this.stack;
  12. }
  13. this.name = 'AbortError';
  14. this.message = message;
  15. }
  16. }
  17. const decorateErrorWithCounts = (error, attemptNumber, options) => {
  18. // Minus 1 from attemptNumber because the first attempt does not count as a retry
  19. const retriesLeft = options.retries - (attemptNumber - 1);
  20. error.attemptNumber = attemptNumber;
  21. error.retriesLeft = retriesLeft;
  22. return error;
  23. };
  24. export default async function pRetry(input, options) {
  25. return new Promise((resolve, reject) => {
  26. options = {
  27. onFailedAttempt() {},
  28. retries: 10,
  29. shouldRetry: () => true,
  30. ...options,
  31. };
  32. const operation = retry.operation(options);
  33. const abortHandler = () => {
  34. operation.stop();
  35. reject(options.signal?.reason);
  36. };
  37. if (options.signal && !options.signal.aborted) {
  38. options.signal.addEventListener('abort', abortHandler, {once: true});
  39. }
  40. const cleanUp = () => {
  41. options.signal?.removeEventListener('abort', abortHandler);
  42. operation.stop();
  43. };
  44. operation.attempt(async attemptNumber => {
  45. try {
  46. const result = await input(attemptNumber);
  47. cleanUp();
  48. resolve(result);
  49. } catch (error) {
  50. try {
  51. if (!(error instanceof Error)) {
  52. throw new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`);
  53. }
  54. if (error instanceof AbortError) {
  55. throw error.originalError;
  56. }
  57. if (error instanceof TypeError && !isNetworkError(error)) {
  58. throw error;
  59. }
  60. decorateErrorWithCounts(error, attemptNumber, options);
  61. if (!(await options.shouldRetry(error))) {
  62. operation.stop();
  63. reject(error);
  64. }
  65. await options.onFailedAttempt(error);
  66. if (!operation.retry(error)) {
  67. throw operation.mainError();
  68. }
  69. } catch (finalError) {
  70. decorateErrorWithCounts(finalError, attemptNumber, options);
  71. cleanUp();
  72. reject(finalError);
  73. }
  74. }
  75. });
  76. });
  77. }