@langchain/core
Version:
Core LangChain.js abstractions and schemas
141 lines (139 loc) • 5.39 kB
JavaScript
const require_index = require('../is-network-error/index.cjs');
//#region src/utils/p-retry/index.js
function validateRetries(retries) {
if (typeof retries === "number") {
if (retries < 0) throw new TypeError("Expected `retries` to be a non-negative number.");
if (Number.isNaN(retries)) throw new TypeError("Expected `retries` to be a valid number or Infinity, got NaN.");
} else if (retries !== void 0) throw new TypeError("Expected `retries` to be a number or Infinity.");
}
function validateNumberOption(name, value, { min = 0, allowInfinity = false } = {}) {
if (value === void 0) return;
if (typeof value !== "number" || Number.isNaN(value)) throw new TypeError(`Expected \`${name}\` to be a number${allowInfinity ? " or Infinity" : ""}.`);
if (!allowInfinity && !Number.isFinite(value)) throw new TypeError(`Expected \`${name}\` to be a finite number.`);
if (value < min) throw new TypeError(`Expected \`${name}\` to be \u2265 ${min}.`);
}
var AbortError = class extends Error {
constructor(message) {
super();
if (message instanceof Error) {
this.originalError = message;
({message} = message);
} else {
this.originalError = new Error(message);
this.originalError.stack = this.stack;
}
this.name = "AbortError";
this.message = message;
}
};
function calculateDelay(retriesConsumed, options) {
const attempt = Math.max(1, retriesConsumed + 1);
const random = options.randomize ? Math.random() + 1 : 1;
let timeout = Math.round(random * options.minTimeout * options.factor ** (attempt - 1));
timeout = Math.min(timeout, options.maxTimeout);
return timeout;
}
function calculateRemainingTime(start, max) {
if (!Number.isFinite(max)) return max;
return max - (performance.now() - start);
}
async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTime, options }) {
const normalizedError = error instanceof Error ? error : /* @__PURE__ */ new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`);
if (normalizedError instanceof AbortError) throw normalizedError.originalError;
const retriesLeft = Number.isFinite(options.retries) ? Math.max(0, options.retries - retriesConsumed) : options.retries;
const maxRetryTime = options.maxRetryTime ?? Number.POSITIVE_INFINITY;
const context = Object.freeze({
error: normalizedError,
attemptNumber,
retriesLeft,
retriesConsumed
});
await options.onFailedAttempt(context);
if (calculateRemainingTime(startTime, maxRetryTime) <= 0) throw normalizedError;
const consumeRetry = await options.shouldConsumeRetry(context);
const remainingTime = calculateRemainingTime(startTime, maxRetryTime);
if (remainingTime <= 0 || retriesLeft <= 0) throw normalizedError;
if (normalizedError instanceof TypeError && !require_index.isNetworkError(normalizedError)) {
if (consumeRetry) throw normalizedError;
options.signal?.throwIfAborted();
return false;
}
if (!await options.shouldRetry(context)) throw normalizedError;
if (!consumeRetry) {
options.signal?.throwIfAborted();
return false;
}
const delayTime = calculateDelay(retriesConsumed, options);
const finalDelay = Math.min(delayTime, remainingTime);
if (finalDelay > 0) await new Promise((resolve, reject) => {
const onAbort = () => {
clearTimeout(timeoutToken);
options.signal?.removeEventListener("abort", onAbort);
reject(options.signal.reason);
};
const timeoutToken = setTimeout(() => {
options.signal?.removeEventListener("abort", onAbort);
resolve();
}, finalDelay);
if (options.unref) timeoutToken.unref?.();
options.signal?.addEventListener("abort", onAbort, { once: true });
});
options.signal?.throwIfAborted();
return true;
}
async function pRetry(input, options = {}) {
options = { ...options };
validateRetries(options.retries);
if (Object.hasOwn(options, "forever")) throw new Error("The `forever` option is no longer supported. For many use-cases, you can set `retries: Infinity` instead.");
options.retries ??= 10;
options.factor ??= 2;
options.minTimeout ??= 1e3;
options.maxTimeout ??= Number.POSITIVE_INFINITY;
options.maxRetryTime ??= Number.POSITIVE_INFINITY;
options.randomize ??= false;
options.onFailedAttempt ??= () => {};
options.shouldRetry ??= () => true;
options.shouldConsumeRetry ??= () => true;
validateNumberOption("factor", options.factor, {
min: 0,
allowInfinity: false
});
validateNumberOption("minTimeout", options.minTimeout, {
min: 0,
allowInfinity: false
});
validateNumberOption("maxTimeout", options.maxTimeout, {
min: 0,
allowInfinity: true
});
validateNumberOption("maxRetryTime", options.maxRetryTime, {
min: 0,
allowInfinity: true
});
if (!(options.factor > 0)) options.factor = 1;
options.signal?.throwIfAborted();
let attemptNumber = 0;
let retriesConsumed = 0;
const startTime = performance.now();
while (Number.isFinite(options.retries) ? retriesConsumed <= options.retries : true) {
attemptNumber++;
try {
options.signal?.throwIfAborted();
const result = await input(attemptNumber);
options.signal?.throwIfAborted();
return result;
} catch (error) {
if (await onAttemptFailure({
error,
attemptNumber,
retriesConsumed,
startTime,
options
})) retriesConsumed++;
}
}
throw new Error("Retry attempts exhausted without throwing an error.");
}
//#endregion
exports.pRetry = pRetry;
//# sourceMappingURL=index.cjs.map