retry.js
2.26 KB
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
/**
* 重试纯函数 —— 瞬态错误判定 + 指数退避计算。
*
* 零副作用、零依赖:被本包 `withRetry` 与 `@drama/llm` 的网关编排层调用,可独立单测。
* 不在此处 sleep、不读时钟、不发请求;只做判定与算术。
*/
/** @typedef {'auth'|'rateLimit'|'transient'|'badRequest'|'timeout'|'aborted'|'unknown'} CallErrorReason */
/**
* 默认重试策略。调用方可在包装时覆盖。
* @typedef {object} RetryPolicy
* @property {number} maxRetries 最大重试次数(不含首次尝试)。
* @property {number} baseDelayMs 首次退避基数。
* @property {number} maxDelayMs 退避上限。
* @property {number} factor 指数因子。
*/
/** @type {RetryPolicy} */
export const DEFAULT_RETRY_POLICY = {
maxRetries: 2,
baseDelayMs: 500,
maxDelayMs: 8000,
factor: 2,
}
/**
* 该 reason 是否属于「可重试的瞬态类」。
* rateLimit / transient / timeout = true;auth / badRequest / aborted / unknown = false。
* @param {CallErrorReason} reason
* @returns {boolean}
*/
export function isRetryableReason(reason) {
return reason === 'rateLimit' || reason === 'transient' || reason === 'timeout'
}
/**
* 在给定策略下,第 `attempt` 次失败后是否还应再试。
* @param {number} attempt 已失败的次数(首次失败时为 1)。
* @param {boolean} retryable 该错误是否可重试。
* @param {RetryPolicy} [policy]
* @returns {boolean}
*/
export function shouldRetry(attempt, retryable, policy = DEFAULT_RETRY_POLICY) {
if (!retryable) return false
return attempt <= policy.maxRetries
}
/**
* 第 `attempt` 次重试前的退避毫秒数(指数退避 + 抖动可选)。
* attempt 从 1 起算(第一次重试)。`retryAfterMs` 若由 429 头给出则优先采用。
* @param {number} attempt
* @param {RetryPolicy} [policy]
* @param {number} [retryAfterMs] 厂商显式给出的等待时间(如 Retry-After)。
* @returns {number}
*/
export function backoffDelayMs(attempt, policy = DEFAULT_RETRY_POLICY, retryAfterMs) {
if (typeof retryAfterMs === 'number' && retryAfterMs >= 0) {
return Math.min(retryAfterMs, policy.maxDelayMs)
}
const raw = policy.baseDelayMs * Math.pow(policy.factor, attempt - 1)
return Math.min(raw, policy.maxDelayMs)
}