Spaces:
Build error
Build error
| import pRetry from "p-retry"; | |
| import PQueueMod from "p-queue"; | |
| const STATUS_NO_RETRY = [ | |
| 400, | |
| 401, | |
| 403, | |
| 404, | |
| 405, | |
| 406, | |
| 407, | |
| 408, | |
| 409, // Conflict | |
| ]; | |
| /** | |
| * A class that can be used to make async calls with concurrency and retry logic. | |
| * | |
| * This is useful for making calls to any kind of "expensive" external resource, | |
| * be it because it's rate-limited, subject to network issues, etc. | |
| * | |
| * Concurrent calls are limited by the `maxConcurrency` parameter, which defaults | |
| * to `Infinity`. This means that by default, all calls will be made in parallel. | |
| * | |
| * Retries are limited by the `maxRetries` parameter, which defaults to 6. This | |
| * means that by default, each call will be retried up to 6 times, with an | |
| * exponential backoff between each attempt. | |
| */ | |
| export class AsyncCaller { | |
| constructor(params) { | |
| Object.defineProperty(this, "maxConcurrency", { | |
| enumerable: true, | |
| configurable: true, | |
| writable: true, | |
| value: void 0 | |
| }); | |
| Object.defineProperty(this, "maxRetries", { | |
| enumerable: true, | |
| configurable: true, | |
| writable: true, | |
| value: void 0 | |
| }); | |
| Object.defineProperty(this, "queue", { | |
| enumerable: true, | |
| configurable: true, | |
| writable: true, | |
| value: void 0 | |
| }); | |
| this.maxConcurrency = params.maxConcurrency ?? Infinity; | |
| this.maxRetries = params.maxRetries ?? 6; | |
| const PQueue = "default" in PQueueMod ? PQueueMod.default : PQueueMod; | |
| this.queue = new PQueue({ concurrency: this.maxConcurrency }); | |
| } | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| call(callable, ...args) { | |
| return this.queue.add(() => pRetry(() => callable(...args).catch((error) => { | |
| // eslint-disable-next-line no-instanceof/no-instanceof | |
| if (error instanceof Error) { | |
| throw error; | |
| } | |
| else { | |
| throw new Error(error); | |
| } | |
| }), { | |
| onFailedAttempt(error) { | |
| if (error.message.startsWith("Cancel") || | |
| error.message.startsWith("TimeoutError") || | |
| error.message.startsWith("AbortError")) { | |
| throw error; | |
| } | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| if (error?.code === "ECONNABORTED") { | |
| throw error; | |
| } | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| const status = error?.response?.status; | |
| if (status && STATUS_NO_RETRY.includes(+status)) { | |
| throw error; | |
| } | |
| }, | |
| retries: this.maxRetries, | |
| randomize: true, | |
| // If needed we can change some of the defaults here, | |
| // but they're quite sensible. | |
| }), { throwOnTimeout: true }); | |
| } | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| callWithOptions(options, callable, ...args) { | |
| // Note this doesn't cancel the underlying request, | |
| // when available prefer to use the signal option of the underlying call | |
| if (options.signal) { | |
| return Promise.race([ | |
| this.call(callable, ...args), | |
| new Promise((_, reject) => { | |
| options.signal?.addEventListener("abort", () => { | |
| reject(new Error("AbortError")); | |
| }); | |
| }), | |
| ]); | |
| } | |
| return this.call(callable, ...args); | |
| } | |
| fetch(...args) { | |
| return this.call(() => fetch(...args).then((res) => (res.ok ? res : Promise.reject(res)))); | |
| } | |
| } | |