async_caller.cjs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.AsyncCaller = void 0;
  7. const p_retry_1 = __importDefault(require("p-retry"));
  8. const p_queue_1 = __importDefault(require("p-queue"));
  9. const fetch_js_1 = require("../singletons/fetch.cjs");
  10. const STATUS_NO_RETRY = [
  11. 400, // Bad Request
  12. 401, // Unauthorized
  13. 403, // Forbidden
  14. 404, // Not Found
  15. 405, // Method Not Allowed
  16. 406, // Not Acceptable
  17. 407, // Proxy Authentication Required
  18. 408, // Request Timeout
  19. ];
  20. const STATUS_IGNORE = [
  21. 409, // Conflict
  22. ];
  23. /**
  24. * A class that can be used to make async calls with concurrency and retry logic.
  25. *
  26. * This is useful for making calls to any kind of "expensive" external resource,
  27. * be it because it's rate-limited, subject to network issues, etc.
  28. *
  29. * Concurrent calls are limited by the `maxConcurrency` parameter, which defaults
  30. * to `Infinity`. This means that by default, all calls will be made in parallel.
  31. *
  32. * Retries are limited by the `maxRetries` parameter, which defaults to 6. This
  33. * means that by default, each call will be retried up to 6 times, with an
  34. * exponential backoff between each attempt.
  35. */
  36. class AsyncCaller {
  37. constructor(params) {
  38. Object.defineProperty(this, "maxConcurrency", {
  39. enumerable: true,
  40. configurable: true,
  41. writable: true,
  42. value: void 0
  43. });
  44. Object.defineProperty(this, "maxRetries", {
  45. enumerable: true,
  46. configurable: true,
  47. writable: true,
  48. value: void 0
  49. });
  50. Object.defineProperty(this, "queue", {
  51. enumerable: true,
  52. configurable: true,
  53. writable: true,
  54. value: void 0
  55. });
  56. Object.defineProperty(this, "onFailedResponseHook", {
  57. enumerable: true,
  58. configurable: true,
  59. writable: true,
  60. value: void 0
  61. });
  62. Object.defineProperty(this, "debug", {
  63. enumerable: true,
  64. configurable: true,
  65. writable: true,
  66. value: void 0
  67. });
  68. this.maxConcurrency = params.maxConcurrency ?? Infinity;
  69. this.maxRetries = params.maxRetries ?? 6;
  70. this.debug = params.debug;
  71. if ("default" in p_queue_1.default) {
  72. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  73. this.queue = new p_queue_1.default.default({
  74. concurrency: this.maxConcurrency,
  75. });
  76. }
  77. else {
  78. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  79. this.queue = new p_queue_1.default({ concurrency: this.maxConcurrency });
  80. }
  81. this.onFailedResponseHook = params?.onFailedResponseHook;
  82. }
  83. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  84. call(callable, ...args) {
  85. const onFailedResponseHook = this.onFailedResponseHook;
  86. return this.queue.add(() => (0, p_retry_1.default)(() => callable(...args).catch((error) => {
  87. // eslint-disable-next-line no-instanceof/no-instanceof
  88. if (error instanceof Error) {
  89. throw error;
  90. }
  91. else {
  92. throw new Error(error);
  93. }
  94. }), {
  95. async onFailedAttempt(error) {
  96. if (error.message.startsWith("Cancel") ||
  97. error.message.startsWith("TimeoutError") ||
  98. error.message.startsWith("AbortError")) {
  99. throw error;
  100. }
  101. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  102. if (error?.code === "ECONNABORTED") {
  103. throw error;
  104. }
  105. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  106. const response = error?.response;
  107. const status = response?.status;
  108. if (status) {
  109. if (STATUS_NO_RETRY.includes(+status)) {
  110. throw error;
  111. }
  112. else if (STATUS_IGNORE.includes(+status)) {
  113. return;
  114. }
  115. if (onFailedResponseHook) {
  116. await onFailedResponseHook(response);
  117. }
  118. }
  119. },
  120. // If needed we can change some of the defaults here,
  121. // but they're quite sensible.
  122. retries: this.maxRetries,
  123. randomize: true,
  124. }), { throwOnTimeout: true });
  125. }
  126. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  127. callWithOptions(options, callable, ...args) {
  128. // Note this doesn't cancel the underlying request,
  129. // when available prefer to use the signal option of the underlying call
  130. if (options.signal) {
  131. return Promise.race([
  132. this.call(callable, ...args),
  133. new Promise((_, reject) => {
  134. options.signal?.addEventListener("abort", () => {
  135. reject(new Error("AbortError"));
  136. });
  137. }),
  138. ]);
  139. }
  140. return this.call(callable, ...args);
  141. }
  142. fetch(...args) {
  143. return this.call(() => (0, fetch_js_1._getFetchImplementation)(this.debug)(...args).then((res) => res.ok ? res : Promise.reject(res)));
  144. }
  145. }
  146. exports.AsyncCaller = AsyncCaller;