index.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. 'use strict';
  2. module.exports = function (fetch, defaults) {
  3. defaults = defaults || {};
  4. if (typeof fetch !== 'function') {
  5. throw new ArgumentError('fetch must be a function');
  6. }
  7. if (typeof defaults !== 'object') {
  8. throw new ArgumentError('defaults must be an object');
  9. }
  10. if (defaults.retries !== undefined && !isPositiveInteger(defaults.retries)) {
  11. throw new ArgumentError('retries must be a positive integer');
  12. }
  13. if (defaults.retryDelay !== undefined && !isPositiveInteger(defaults.retryDelay) && typeof defaults.retryDelay !== 'function') {
  14. throw new ArgumentError('retryDelay must be a positive integer or a function returning a positive integer');
  15. }
  16. if (defaults.retryOn !== undefined && !Array.isArray(defaults.retryOn) && typeof defaults.retryOn !== 'function') {
  17. throw new ArgumentError('retryOn property expects an array or function');
  18. }
  19. var baseDefaults = {
  20. retries: 3,
  21. retryDelay: 1000,
  22. retryOn: [],
  23. };
  24. defaults = Object.assign(baseDefaults, defaults);
  25. return function fetchRetry(input, init) {
  26. var retries = defaults.retries;
  27. var retryDelay = defaults.retryDelay;
  28. var retryOn = defaults.retryOn;
  29. if (init && init.retries !== undefined) {
  30. if (isPositiveInteger(init.retries)) {
  31. retries = init.retries;
  32. } else {
  33. throw new ArgumentError('retries must be a positive integer');
  34. }
  35. }
  36. if (init && init.retryDelay !== undefined) {
  37. if (isPositiveInteger(init.retryDelay) || (typeof init.retryDelay === 'function')) {
  38. retryDelay = init.retryDelay;
  39. } else {
  40. throw new ArgumentError('retryDelay must be a positive integer or a function returning a positive integer');
  41. }
  42. }
  43. if (init && init.retryOn) {
  44. if (Array.isArray(init.retryOn) || (typeof init.retryOn === 'function')) {
  45. retryOn = init.retryOn;
  46. } else {
  47. throw new ArgumentError('retryOn property expects an array or function');
  48. }
  49. }
  50. // eslint-disable-next-line no-undef
  51. return new Promise(function (resolve, reject) {
  52. var wrappedFetch = function (attempt) {
  53. // As of node 18, this is no longer needed since node comes with native support for fetch:
  54. /* istanbul ignore next */
  55. var _input =
  56. typeof Request !== 'undefined' && input instanceof Request
  57. ? input.clone()
  58. : input;
  59. fetch(_input, init)
  60. .then(function (response) {
  61. if (Array.isArray(retryOn) && retryOn.indexOf(response.status) === -1) {
  62. resolve(response);
  63. } else if (typeof retryOn === 'function') {
  64. try {
  65. // eslint-disable-next-line no-undef
  66. return Promise.resolve(retryOn(attempt, null, response))
  67. .then(function (retryOnResponse) {
  68. if(retryOnResponse) {
  69. retry(attempt, null, response);
  70. } else {
  71. resolve(response);
  72. }
  73. }).catch(reject);
  74. } catch (error) {
  75. reject(error);
  76. }
  77. } else {
  78. if (attempt < retries) {
  79. retry(attempt, null, response);
  80. } else {
  81. resolve(response);
  82. }
  83. }
  84. })
  85. .catch(function (error) {
  86. if (typeof retryOn === 'function') {
  87. try {
  88. // eslint-disable-next-line no-undef
  89. Promise.resolve(retryOn(attempt, error, null))
  90. .then(function (retryOnResponse) {
  91. if(retryOnResponse) {
  92. retry(attempt, error, null);
  93. } else {
  94. reject(error);
  95. }
  96. })
  97. .catch(function(error) {
  98. reject(error);
  99. });
  100. } catch(error) {
  101. reject(error);
  102. }
  103. } else if (attempt < retries) {
  104. retry(attempt, error, null);
  105. } else {
  106. reject(error);
  107. }
  108. });
  109. };
  110. function retry(attempt, error, response) {
  111. var delay = (typeof retryDelay === 'function') ?
  112. retryDelay(attempt, error, response) : retryDelay;
  113. setTimeout(function () {
  114. wrappedFetch(++attempt);
  115. }, delay);
  116. }
  117. wrappedFetch(0);
  118. });
  119. };
  120. };
  121. function isPositiveInteger(value) {
  122. return Number.isInteger(value) && value >= 0;
  123. }
  124. function ArgumentError(message) {
  125. this.name = 'ArgumentError';
  126. this.message = message;
  127. }