RequestExecutor
Низкоуровневая обёртка для одного REST-запроса с повторными попытками, таймаутом (через AbortController), поддержкой заголовка Retry-After и backoff'ом. Именно этот класс createRestClient() использует внутри себя для повторов — используйте его напрямую, когда нужны повторы/backoff без остального RestClient (кэширования, ограничения частоты, аутентификации, …).
import { RequestExecutor } from 'rest-pipeline-js'
const executor = new RequestExecutor({
baseURL: 'https://api.example.com',
retry: {
attempts: 3,
delayMs: 500,
backoffMultiplier: 2,
retriableStatus: [429, 500, 502, 503],
maxRetryAfterMs: 30000, // ограничить Retry-After 30 с
},
})
// 5-й аргумент: внешний AbortSignal (например, из orchestrator.abort())
const res = await executor.execute('/data', undefined, 3, 5000, signal)Когда сервер возвращает заголовок Retry-After (число секунд или HTTP-дата), эта задержка имеет приоритет над формулой backoff. Значения, превышающие maxRetryAfterMs, ограничиваются этим потолком. Таймаут обеспечивается через AbortController — отменяется сам HTTP-запрос, а не только промис.
Конструктор
new RequestExecutor(httpConfig: HttpConfig)Принимает тот же HttpConfig, что и createRestClient() — на практике RequestExecutor сам читает только httpConfig.retry, а остальное использует для построения своего внутреннего HTTP-клиента.
Методы
execute(command, reqConfig?, retryCount?, timeoutMs?, externalSignal?)
execute<T = unknown>(
command: string,
reqConfig?: RestRequestConfig,
retryCount?: number,
timeoutMs?: number,
externalSignal?: AbortSignal,
): Promise<ApiResponse<T>>Выполняет один запрос с повторами/backoff/таймаутом.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
command | string | — | URL запроса (относительно baseURL, если задан) |
reqConfig | RestRequestConfig | — | Та же конфигурация запроса, что принимают методы RestClient |
retryCount | number | httpConfig.retry.attempts ?? 0 | Переопределить количество повторов для этого вызова |
timeoutMs | number | 10000 | Таймаут на попытку в мс, обеспечивается через AbortController |
externalSignal | AbortSignal | — | Внешний сигнал отмены (например, из orchestrator.abort()) |
Стратегии jitter
retry.jitterStrategy управляет тем, как случайность добавляется к вычисленной задержке backoff (на Retry-After, который всегда используется как есть, это не влияет):
"fixed"(по умолчанию) —delayMs * backoffMultiplier^(attempt-1)плюс до +10% случайного джиттера сверху. Обратно совместимо; задержка никогда не опускается ниже чистого значения backoff."full"—delay = random(0, delayMs * backoffMultiplier^(attempt-1)). Лучше распределяет множество одновременных повторов (избегает синхронизированных «штормов» повторов, бьющих в backend в один и тот же момент), ценой того, что отдельные задержки иногда оказываются намного короче номинального backoff."decorrelated"—delay = min(cap, random(delayMs, prevDelay * 3)), гдеprevDelayначинается сdelayMsи обновляется после каждой попытки, аcapравенdelayMs * backoffMultiplier^attempts. Распределяет одновременные повторы даже лучше, чем"full", поскольку следующая задержка каждого клиента зависит от его собственной предыдущей.
Оба алгоритма — из статьи AWS Exponential Backoff and Jitter — используйте их, когда против одного и того же backend одновременно могут повторять запросы много экземпляров вашего приложения.
const executor = new RequestExecutor({
baseURL: 'https://api.example.com',
retry: { attempts: 5, delayMs: 200, backoffMultiplier: 2, jitterStrategy: 'full' },
})