UNPKG

@tanstack/query-core

Version:

The framework agnostic core that powers TanStack Query

168 lines (167 loc) • 5.33 kB
import { noop, sleep } from "./utils.js"; import { isServer } from "./environmentManager.js"; import { focusManager } from "./focusManager.js"; import { onlineManager } from "./onlineManager.js"; //#region src/retryer.ts /** * The default `retryDelay`: doubles with every failure, starting at 1 second, up to 30 seconds. * @param failureCount - The number of failures so far. * @returns The delay before the next retry, in milliseconds. */ function defaultRetryDelay(failureCount) { return Math.min(1e3 * 2 ** failureCount, 3e4); } /** * Checks whether a fetch can run for the given network mode. * @param networkMode - The `networkMode` option. Defaults to `'online'`. * @returns `true` if the network mode is not `'online'`, or if it is and the client is online. */ function canFetch(networkMode) { return (networkMode ?? "online") === "online" ? onlineManager.isOnline() : true; } /** * The error thrown by a `Retryer` (and surfaced to `query.promise`/`mutation`) when a fetch is cancelled, e.g. via * `query.cancel()`. `revert`, if `true`, tells the caller to restore the state the query was in before the fetch * started instead of surfacing the error. `silent`, if `true`, tells the caller to suppress this error and instead * resolve with the promise of the fetch that triggered the cancellation. * @example * ```ts * query.cancel() * * try { * await query.promise * } catch (error) { * if (error instanceof CancelledError) { * // the fetch was cancelled, e.g. via `query.cancel()` * } * } * ``` */ var CancelledError = class extends Error { constructor(options) { super("CancelledError"); this.revert = options?.revert; this.silent = options?.silent; } }; /** * Checks whether a value is a `CancelledError`. * @param value - The value to check. * @returns `true` if `value` is a `CancelledError`. * @deprecated Use instanceof `CancelledError` instead. */ function isCancelledError(value) { return value instanceof CancelledError; } /** * Creates a retryer, which runs a function and retries it on failure according to the `retry` and * `retryDelay` options. It pauses while the network mode doesn't allow fetching or `canRun` returns * `false`, and can be cancelled. Queries and mutations use it to run their `queryFn` and * `mutationFn`. * @param config - The function to run, the retry options, and the callbacks for failures, pausing, * continuing, and cancellation. * @returns The retryer, whose `promise` settles with the result of the function. */ function createRetryer(config) { let isRetryCancelled = false; let failureCount = 0; let continueFn; let status = "pending"; let promiseResolve; let promiseReject; const promise = new Promise((resolve, reject) => { promiseResolve = resolve; promiseReject = reject; }); promise.catch(noop); const isResolved = () => 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?.(); status = "resolved"; promiseResolve(value); } }; const reject = (value) => { if (!isResolved()) { continueFn?.(); status = "rejected"; promiseReject(value); } }; const pause = (canResume) => { return new Promise((continueResolve) => { continueFn = (value) => { if (isResolved() || canResume()) 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(canContinue); }).then(() => { if (isRetryCancelled) reject(error); else run(); }); }); }; return { promise, status: () => status, cancel, continue: () => { continueFn?.(); return promise; }, cancelRetry, continueRetry, canStart, start: () => { if (canStart()) run(); else pause(canStart).then(run); return promise; } }; } //#endregion export { CancelledError, canFetch, createRetryer, isCancelledError }; //# sourceMappingURL=retryer.js.map