|
1 |
| -interface RetryOperation { |
2 |
| - retryCount: number | "infinite"; |
3 |
| - retryDelay: number; |
4 |
| - retryCallback: (payload?: any) => any; |
5 |
| - onErrorCallback: (error?: Error, currentRetryCount?: number) => void; |
6 |
| - onSuccessCallback: (response?: any) => void; |
7 |
| - afterLastAttemptErrorCallback?: (error?: any) => void; |
| 1 | +interface RetryOperation<TResponse> { |
| 2 | + retryCount?: number | "infinite"; |
| 3 | + retryDelay?: number; |
| 4 | + retryCallback?: (payload?: any) => TResponse | Promise<TResponse>; |
| 5 | + onErrorCallback?: ( |
| 6 | + error?: Error, |
| 7 | + currentRetryCount?: number |
| 8 | + ) => void | Promise<void>; |
| 9 | + onSuccessCallback?: (response: TResponse) => void | Promise<void>; |
| 10 | + afterLastAttemptErrorCallback?: (error?: any) => void | Promise<void>; |
8 | 11 | incrementalDelayFactor?: number; // Optional factor to increase the delay
|
| 12 | + logCallback?: (message: string) => void; // Optional logging mechanism |
| 13 | + enableLogging?: boolean; // Enable or disable logging |
| 14 | + retryCondition?: RetryCondition; // Custom condition to decide retry continuation |
9 | 15 | }
|
10 | 16 |
|
11 |
| -interface RetryAsyncOperationExtended extends RetryOperation { |
12 |
| - retryAsyncCallback: () => Promise<void>; |
13 |
| -} |
| 17 | +type RetryCondition = (currentRetryCount: number, lastError: any) => boolean; |
14 | 18 |
|
15 |
| -type RetryAsyncOperation = Omit<RetryAsyncOperationExtended, "retryCallback">; |
| 19 | +const MAX_DELAY = 30000; // Maximum delay in milliseconds |
16 | 20 |
|
17 | 21 | // Utility function to introduce a delay
|
18 | 22 | const sleep = (delay: number): Promise<void> => {
|
19 | 23 | return new Promise((resolve) => setTimeout(resolve, delay));
|
20 | 24 | };
|
21 | 25 |
|
22 |
| -// Synchronous retry operation with error handling and exponential delay |
23 |
| -async function retryOperation({ |
| 26 | +/** |
| 27 | + * Retries a callback function with error handling and exponential delay. |
| 28 | + * Supports both synchronous and asynchronous functions. |
| 29 | + * |
| 30 | + * @param retryCallback - The function to retry (sync/async). |
| 31 | + * @param onErrorCallback - Called on each retry failure. |
| 32 | + * @param onSuccessCallback - Called on successful retry. |
| 33 | + * @param retryCount - Maximum retry attempts or "infinite". |
| 34 | + * @param retryDelay - Initial retry delay in milliseconds. |
| 35 | + * @param incrementalDelayFactor - Multiplier for delay after each retry. |
| 36 | + * @param afterLastAttemptErrorCallback - Called after the last failed attempt. |
| 37 | + * @param logCallback - Optional logging mechanism for debugging. |
| 38 | + * @param enableLogging - Enables/disables logging (default: true). |
| 39 | + * @param retryCondition - Custom condition to decide if retry should continue. |
| 40 | + */ |
| 41 | +async function retryOperation<TResponse>({ |
24 | 42 | retryCallback,
|
25 | 43 | onErrorCallback,
|
26 | 44 | onSuccessCallback,
|
27 | 45 | afterLastAttemptErrorCallback,
|
28 | 46 | retryCount = 3, // Default retry count is 3
|
29 | 47 | retryDelay = 1000, // Default is 1 second
|
30 | 48 | incrementalDelayFactor = 1.5, // Default factor is 1.5
|
31 |
| -}: RetryOperation): Promise<void> { |
| 49 | + logCallback, |
| 50 | + enableLogging = true, // Default to true |
| 51 | + retryCondition, |
| 52 | +}: RetryOperation<TResponse>): Promise<void> { |
32 | 53 | let currentRetryCount = 0;
|
33 | 54 | let lastError: any = null;
|
34 | 55 | let currentDelay = retryDelay;
|
35 | 56 |
|
36 |
| - // Loop until retries are exhausted or successful |
37 |
| - while (retryCount === "infinite" || currentRetryCount <= retryCount) { |
38 |
| - try { |
39 |
| - if (currentDelay > 0 && currentRetryCount > 0) { |
40 |
| - await sleep(currentDelay); // Wait for the delay if it's not the first attempt |
41 |
| - } |
42 |
| - |
43 |
| - const response = retryCallback(); |
44 |
| - onSuccessCallback(response); // Call success callback on success |
45 |
| - return; |
46 |
| - } catch (error) { |
47 |
| - lastError = error; |
48 |
| - onErrorCallback(error as Error, currentRetryCount); // Handle error with retry count |
49 |
| - currentDelay *= incrementalDelayFactor; // Increase the delay for the next retry |
50 |
| - currentRetryCount++; |
| 57 | + // Helper function to log messages if logging is enabled |
| 58 | + const log = (message: string) => { |
| 59 | + if (enableLogging && logCallback) { |
| 60 | + logCallback(message); |
51 | 61 | }
|
52 |
| - } |
53 |
| - |
54 |
| - // Call the final error callback if retries are exhausted |
55 |
| - if (afterLastAttemptErrorCallback) { |
56 |
| - afterLastAttemptErrorCallback(lastError); |
57 |
| - } |
58 |
| -} |
59 |
| - |
60 |
| -// Asynchronous retry operation with error handling and exponential delay |
61 |
| -async function retryAsyncOperation({ |
62 |
| - retryAsyncCallback, |
63 |
| - onErrorCallback, |
64 |
| - onSuccessCallback, |
65 |
| - afterLastAttemptErrorCallback, |
66 |
| - retryCount = 3, // Default retry count is 3 |
67 |
| - retryDelay = 1000, // Default is 1 second |
68 |
| - incrementalDelayFactor = 1.5, // Default factor is 1.5 |
69 |
| -}: RetryAsyncOperation): Promise<void> { |
70 |
| - let currentRetryCount = 0; |
71 |
| - let lastError: any = null; |
72 |
| - let currentDelay = retryDelay; |
| 62 | + }; |
73 | 63 |
|
74 | 64 | // Loop until retries are exhausted or successful
|
75 |
| - while (retryCount === "infinite" || currentRetryCount <= retryCount) { |
| 65 | + while ( |
| 66 | + (retryCount === "infinite" || currentRetryCount <= retryCount) && |
| 67 | + (!retryCondition || retryCondition(currentRetryCount, lastError)) |
| 68 | + ) { |
76 | 69 | try {
|
77 | 70 | if (currentDelay > 0 && currentRetryCount > 0) {
|
| 71 | + log(`Attempt ${currentRetryCount}: Waiting for ${currentDelay} ms`); |
78 | 72 | await sleep(currentDelay); // Wait for the delay if it's not the first attempt
|
79 | 73 | }
|
80 | 74 |
|
81 |
| - const response = await retryAsyncCallback(); |
82 |
| - onSuccessCallback(response); // Call success callback on success |
| 75 | + log(`Attempt ${currentRetryCount}: Executing callback`); |
| 76 | + const response = await retryCallback(); |
| 77 | + |
| 78 | + log(`Attempt ${currentRetryCount}: Success`); |
| 79 | + if (onSuccessCallback) { |
| 80 | + try { |
| 81 | + const successResponse = onSuccessCallback(response); |
| 82 | + if (successResponse instanceof Promise) await successResponse; |
| 83 | + } catch (successCallbackError) { |
| 84 | + log(`Error in onSuccessCallback: ${successCallbackError}`); |
| 85 | + } |
| 86 | + } |
83 | 87 | return;
|
84 | 88 | } catch (error) {
|
85 | 89 | lastError = error;
|
86 |
| - onErrorCallback(error as Error, currentRetryCount); // Handle error with retry count |
87 |
| - currentDelay *= incrementalDelayFactor; // Increase the delay for the next retry |
| 90 | + log(`Attempt ${currentRetryCount}: Failed with error: ${error}`); |
| 91 | + if (onErrorCallback) { |
| 92 | + try { |
| 93 | + const errorResponse = onErrorCallback( |
| 94 | + error as Error, |
| 95 | + currentRetryCount |
| 96 | + ); |
| 97 | + if (errorResponse instanceof Promise) await errorResponse; |
| 98 | + } catch (callbackError) { |
| 99 | + log(`Error in onErrorCallback: ${callbackError}`); |
| 100 | + } |
| 101 | + } |
| 102 | + currentDelay = Math.min(currentDelay * incrementalDelayFactor, MAX_DELAY); // Increase delay |
88 | 103 | currentRetryCount++;
|
89 | 104 | }
|
90 | 105 | }
|
91 | 106 |
|
92 | 107 | // Call the final error callback if retries are exhausted
|
93 | 108 | if (afterLastAttemptErrorCallback) {
|
94 |
| - afterLastAttemptErrorCallback(lastError); |
| 109 | + try { |
| 110 | + log(`Retries exhausted. Invoking afterLastAttemptErrorCallback`); |
| 111 | + const finalErrorResponse = afterLastAttemptErrorCallback(lastError); |
| 112 | + if (finalErrorResponse instanceof Promise) await finalErrorResponse; |
| 113 | + } catch (finalCallbackError) { |
| 114 | + log(`Error in afterLastAttemptErrorCallback: ${finalCallbackError}`); |
| 115 | + } |
95 | 116 | }
|
96 | 117 | }
|
97 | 118 |
|
98 |
| -export { |
99 |
| - retryOperation, |
100 |
| - retryAsyncOperation, |
101 |
| - RetryOperation, |
102 |
| - RetryAsyncOperation, |
103 |
| -}; |
| 119 | +export { retryOperation, RetryOperation, RetryCondition }; |
0 commit comments