forked from vercel/ai
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathretry-with-exponential-backoff.ts
83 lines (74 loc) · 2.31 KB
/
retry-with-exponential-backoff.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import { APICallError } from '@ai-sdk/provider';
import { getErrorMessage, isAbortError } from '@ai-sdk/provider-utils';
import { delay } from './delay';
import { RetryError } from './retry-error';
export type RetryFunction = <OUTPUT>(
fn: () => PromiseLike<OUTPUT>,
) => PromiseLike<OUTPUT>;
/**
The `retryWithExponentialBackoff` strategy retries a failed API call with an exponential backoff.
You can configure the maximum number of retries, the initial delay, and the backoff factor.
*/
export const retryWithExponentialBackoff =
({
maxRetries = 2,
initialDelayInMs = 2000,
backoffFactor = 2,
} = {}): RetryFunction =>
async <OUTPUT>(f: () => PromiseLike<OUTPUT>) =>
_retryWithExponentialBackoff(f, {
maxRetries,
delayInMs: initialDelayInMs,
backoffFactor,
});
async function _retryWithExponentialBackoff<OUTPUT>(
f: () => PromiseLike<OUTPUT>,
{
maxRetries,
delayInMs,
backoffFactor,
}: { maxRetries: number; delayInMs: number; backoffFactor: number },
errors: unknown[] = [],
): Promise<OUTPUT> {
try {
return await f();
} catch (error) {
if (isAbortError(error)) {
throw error; // don't retry when the request was aborted
}
if (maxRetries === 0) {
throw error; // don't wrap the error when retries are disabled
}
const errorMessage = getErrorMessage(error);
const newErrors = [...errors, error];
const tryNumber = newErrors.length;
if (tryNumber > maxRetries) {
throw new RetryError({
message: `Failed after ${tryNumber} attempts. Last error: ${errorMessage}`,
reason: 'maxRetriesExceeded',
errors: newErrors,
});
}
if (
error instanceof Error &&
APICallError.isAPICallError(error) &&
error.isRetryable === true &&
tryNumber <= maxRetries
) {
await delay(delayInMs);
return _retryWithExponentialBackoff(
f,
{ maxRetries, delayInMs: backoffFactor * delayInMs, backoffFactor },
newErrors,
);
}
if (tryNumber === 1) {
throw error; // don't wrap the error when a non-retryable error occurs on the first try
}
throw new RetryError({
message: `Failed after ${tryNumber} attempts with non-retryable error: '${errorMessage}'`,
reason: 'errorNotRetryable',
errors: newErrors,
});
}
}