refactor(core/stdlib): improve retry function implementation and error handling

This commit is contained in:
2026-03-26 06:09:54 +07:00
parent a61fb85088
commit 8d8ea734d1
+28 -22
View File
@@ -9,35 +9,40 @@ export interface RetryOptions {
} }
export type RetryFunction<Return> = ( export type RetryFunction<Return> = (
args: { args: {
count: number; count: number;
stop: (error: any) => void; stop: (error: any) => void;
}, },
) => Promise<Return>; ) => Promise<Return>;
const RetryEarlyExit = Symbol('RetryEarlyExit'); class RetryEarlyExitError {
cause: any;
constructor(cause: any) {
this.cause = cause;
}
}
/** /**
* @name retry * @name retry
* @category Async * @category Async
* @description Retries a function a specified number of times with a delay between each retry * @description Retries a function a specified number of times with a delay between each retry
* *
* @param {Promise<unknown>} fn - The function to retry * @param {Promise<unknown>} fn - The function to retry
* @param {RetryOptions} options - The options for the retry * @param {RetryOptions} options - The options for the retry
* @returns {Promise<unknown>} - The result of the function * @returns {Promise<unknown>} - The result of the function
* *
* @example * @example
* const result = await retry(() => { * const result = await retry(() => {
* return fetch('https://jsonplaceholder.typicode.com/todos/1') * return fetch('https://jsonplaceholder.typicode.com/todos/1')
* .then(response => response.json()) * .then(response => response.json())
* }); * });
* *
* @example * @example
* const result = await retry(() => { * const result = await retry(() => {
* return fetch('https://jsonplaceholder.typicode.com/todos/1') * return fetch('https://jsonplaceholder.typicode.com/todos/1')
* .then(response => response.json()) * .then(response => response.json())
* }, { times: 3, delay: 1000 }); * }, { times: 3, delay: 1000 });
* *
* @since 0.0.8 * @since 0.0.8
*/ */
export async function retry<Return>( export async function retry<Return>(
@@ -50,24 +55,25 @@ export async function retry<Return>(
shouldRetry, shouldRetry,
} = options; } = options;
const wrappedFn = tryIt(fn);
const delayFn = isFunction(delay) ? delay : null;
const delayMs = delayFn ? 0 : delay as number;
const stop = (error?: any): never => {
throw new RetryEarlyExitError(error);
};
let lastError: Error | null = null;
let count = 1; let count = 1;
let lastError: Error = new Error('Retry failed');
while (count <= times) { while (count <= times) {
const metadata = { const { error, data } = await wrappedFn({ count, stop });
count,
stop: (error?: any) => {
throw { [RetryEarlyExit]: error };
},
};
const { error, data } = await tryIt(fn)(metadata);
if (!error) if (!error)
return data; return data;
if (RetryEarlyExit in error) if (error instanceof RetryEarlyExitError)
throw error[RetryEarlyExit]; throw error.cause;
if (shouldRetry && !shouldRetry(error, count)) if (shouldRetry && !shouldRetry(error, count))
throw error; throw error;
@@ -77,12 +83,12 @@ export async function retry<Return>(
// Don't delay after the last attempt // Don't delay after the last attempt
if (count <= times) { if (count <= times) {
const delayMs = isFunction(delay) ? delay(count) : delay; const ms = delayFn ? delayFn(count) : delayMs;
if (delayMs > 0) if (ms > 0)
await sleep(delayMs); await sleep(ms);
} }
} }
throw lastError; throw lastError!;
} }