index.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.retryAsPromised = exports.applyJitter = exports.TimeoutError = void 0;
  4. class TimeoutError extends Error {
  5. constructor(message, previousError) {
  6. super(message);
  7. this.name = "TimeoutError";
  8. this.previous = previousError;
  9. }
  10. }
  11. exports.TimeoutError = TimeoutError;
  12. function matches(match, err) {
  13. if (typeof match === 'function') {
  14. try {
  15. if (err instanceof match)
  16. return true;
  17. }
  18. catch (_) {
  19. return !!match(err);
  20. }
  21. }
  22. if (match === err.toString())
  23. return true;
  24. if (match === err.message)
  25. return true;
  26. return match instanceof RegExp
  27. && (match.test(err.message) || match.test(err.toString()));
  28. }
  29. function applyJitter(delayMs, maxJitterMs) {
  30. const newDelayMs = delayMs + (Math.random() * maxJitterMs * (Math.random() > 0.5 ? 1 : -1));
  31. return Math.max(0, newDelayMs);
  32. }
  33. exports.applyJitter = applyJitter;
  34. function retryAsPromised(callback, optionsInput) {
  35. if (!callback || !optionsInput) {
  36. throw new Error('retry-as-promised must be passed a callback and a options set');
  37. }
  38. optionsInput = (typeof optionsInput === "number" ? { max: optionsInput } : optionsInput);
  39. const options = {
  40. $current: "$current" in optionsInput ? optionsInput.$current : 1,
  41. max: optionsInput.max,
  42. timeout: optionsInput.timeout || undefined,
  43. match: optionsInput.match ? Array.isArray(optionsInput.match) ? optionsInput.match : [optionsInput.match] : [],
  44. backoffBase: optionsInput.backoffBase === undefined ? 100 : optionsInput.backoffBase,
  45. backoffExponent: optionsInput.backoffExponent || 1.1,
  46. backoffJitter: optionsInput.backoffJitter || 0.0,
  47. report: optionsInput.report,
  48. name: optionsInput.name || callback.name || 'unknown'
  49. };
  50. if (options.match && !Array.isArray(options.match))
  51. options.match = [options.match];
  52. if (options.report)
  53. options.report('Trying ' + options.name + ' #' + options.$current + ' at ' + new Date().toLocaleTimeString(), options);
  54. return new Promise(function (resolve, reject) {
  55. let timeout;
  56. let backoffTimeout;
  57. let lastError;
  58. if (options.timeout) {
  59. timeout = setTimeout(function () {
  60. if (backoffTimeout)
  61. clearTimeout(backoffTimeout);
  62. reject(new TimeoutError(options.name + ' timed out', lastError));
  63. }, options.timeout);
  64. }
  65. Promise.resolve(callback({ current: options.$current }))
  66. .then(resolve)
  67. .then(function () {
  68. if (timeout)
  69. clearTimeout(timeout);
  70. if (backoffTimeout)
  71. clearTimeout(backoffTimeout);
  72. })
  73. .catch(function (err) {
  74. if (timeout)
  75. clearTimeout(timeout);
  76. if (backoffTimeout)
  77. clearTimeout(backoffTimeout);
  78. lastError = err;
  79. if (options.report)
  80. options.report((err && err.toString()) || err, options, err);
  81. // Should not retry if max has been reached
  82. var shouldRetry = options.$current < options.max;
  83. if (!shouldRetry)
  84. return reject(err);
  85. shouldRetry = options.match.length === 0 || options.match.some(function (match) {
  86. return matches(match, err);
  87. });
  88. if (!shouldRetry)
  89. return reject(err);
  90. var retryDelay = options.backoffBase * Math.pow(options.backoffExponent, options.$current - 1);
  91. const backoffJitter = options.backoffJitter;
  92. if (backoffJitter !== undefined) {
  93. retryDelay = applyJitter(retryDelay, backoffJitter);
  94. }
  95. // Do some accounting
  96. options.$current++;
  97. if (options.report)
  98. options.report(`Retrying ${options.name} (${options.$current})`, options);
  99. if (retryDelay) {
  100. // Use backoff function to ease retry rate
  101. if (options.report)
  102. options.report(`Delaying retry of ${options.name} by ${retryDelay}`, options);
  103. backoffTimeout = setTimeout(function () {
  104. retryAsPromised(callback, options)
  105. .then(resolve)
  106. .catch(reject);
  107. }, retryDelay);
  108. }
  109. else {
  110. retryAsPromised(callback, options)
  111. .then(resolve)
  112. .catch(reject);
  113. }
  114. });
  115. });
  116. }
  117. exports.retryAsPromised = retryAsPromised;
  118. ;
  119. exports.default = retryAsPromised;