mirror of
https://github.com/hanzoai/agency.git
synced 2026-11-02 19:13:28 +00:00
133 lines
3.6 KiB
Plaintext
133 lines
3.6 KiB
Plaintext
// src/retryer.ts
|
|
import { focusManager } from "./focusManager.js";
|
|
import { onlineManager } from "./onlineManager.js";
|
|
import { pendingThenable } from "./thenable.js";
|
|
import { isServer, sleep } from "./utils.js";
|
|
function defaultRetryDelay(failureCount) {
|
|
return Math.min(1e3 * 2 ** failureCount, 3e4);
|
|
}
|
|
function canFetch(networkMode) {
|
|
return (networkMode ?? "online") === "online" ? onlineManager.isOnline() : true;
|
|
}
|
|
var CancelledError = class extends Error {
|
|
constructor(options) {
|
|
super("CancelledError");
|
|
this.revert = options?.revert;
|
|
this.silent = options?.silent;
|
|
}
|
|
};
|
|
function isCancelledError(value) {
|
|
return value instanceof CancelledError;
|
|
}
|
|
function createRetryer(config) {
|
|
let isRetryCancelled = false;
|
|
let failureCount = 0;
|
|
let continueFn;
|
|
const thenable = pendingThenable();
|
|
const isResolved = () => thenable.status !== "pending";
|
|
const cancel = (cancelOptions) => {
|
|
if (!isResolved()) {
|
|
const error = new CancelledError(cancelOptions);
|
|
reject(error);
|
|
config.onCancel?.(error);
|
|
}
|
|
};
|
|
const cancelRetry = () => {
|
|
isRetryCancelled = true;
|
|
};
|
|
const continueRetry = () => {
|
|
isRetryCancelled = false;
|
|
};
|
|
const canContinue = () => focusManager.isFocused() && (config.networkMode === "always" || onlineManager.isOnline()) && config.canRun();
|
|
const canStart = () => canFetch(config.networkMode) && config.canRun();
|
|
const resolve = (value) => {
|
|
if (!isResolved()) {
|
|
continueFn?.();
|
|
thenable.resolve(value);
|
|
}
|
|
};
|
|
const reject = (value) => {
|
|
if (!isResolved()) {
|
|
continueFn?.();
|
|
thenable.reject(value);
|
|
}
|
|
};
|
|
const pause = () => {
|
|
return new Promise((continueResolve) => {
|
|
continueFn = (value) => {
|
|
if (isResolved() || canContinue()) {
|
|
continueResolve(value);
|
|
}
|
|
};
|
|
config.onPause?.();
|
|
}).then(() => {
|
|
continueFn = void 0;
|
|
if (!isResolved()) {
|
|
config.onContinue?.();
|
|
}
|
|
});
|
|
};
|
|
const run = () => {
|
|
if (isResolved()) {
|
|
return;
|
|
}
|
|
let promiseOrValue;
|
|
const initialPromise = failureCount === 0 ? config.initialPromise : void 0;
|
|
try {
|
|
promiseOrValue = initialPromise ?? config.fn();
|
|
} catch (error) {
|
|
promiseOrValue = Promise.reject(error);
|
|
}
|
|
Promise.resolve(promiseOrValue).then(resolve).catch((error) => {
|
|
if (isResolved()) {
|
|
return;
|
|
}
|
|
const retry = config.retry ?? (isServer ? 0 : 3);
|
|
const retryDelay = config.retryDelay ?? defaultRetryDelay;
|
|
const delay = typeof retryDelay === "function" ? retryDelay(failureCount, error) : retryDelay;
|
|
const shouldRetry = retry === true || typeof retry === "number" && failureCount < retry || typeof retry === "function" && retry(failureCount, error);
|
|
if (isRetryCancelled || !shouldRetry) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
failureCount++;
|
|
config.onFail?.(failureCount, error);
|
|
sleep(delay).then(() => {
|
|
return canContinue() ? void 0 : pause();
|
|
}).then(() => {
|
|
if (isRetryCancelled) {
|
|
reject(error);
|
|
} else {
|
|
run();
|
|
}
|
|
});
|
|
});
|
|
};
|
|
return {
|
|
promise: thenable,
|
|
status: () => thenable.status,
|
|
cancel,
|
|
continue: () => {
|
|
continueFn?.();
|
|
return thenable;
|
|
},
|
|
cancelRetry,
|
|
continueRetry,
|
|
canStart,
|
|
start: () => {
|
|
if (canStart()) {
|
|
run();
|
|
} else {
|
|
pause().then(run);
|
|
}
|
|
return thenable;
|
|
}
|
|
};
|
|
}
|
|
export {
|
|
CancelledError,
|
|
canFetch,
|
|
createRetryer,
|
|
isCancelledError
|
|
};
|
|
//# sourceMappingURL=retryer.js.map |