Skip to content

RequestExecutor

Низкоуровневая обёртка для одного REST-запроса с повторными попытками, таймаутом (через AbortController), поддержкой заголовка Retry-After и backoff'ом. Именно этот класс createRestClient() использует внутри себя для повторов — используйте его напрямую, когда нужны повторы/backoff без остального RestClient (кэширования, ограничения частоты, аутентификации, …).

js
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-запрос, а не только промис.

Конструктор

ts
new RequestExecutor(httpConfig: HttpConfig)

Принимает тот же HttpConfig, что и createRestClient() — на практике RequestExecutor сам читает только httpConfig.retry, а остальное использует для построения своего внутреннего HTTP-клиента.

Методы

execute(command, reqConfig?, retryCount?, timeoutMs?, externalSignal?)

ts
execute<T = unknown>(
  command: string,
  reqConfig?: RestRequestConfig,
  retryCount?: number,
  timeoutMs?: number,
  externalSignal?: AbortSignal,
): Promise<ApiResponse<T>>

Выполняет один запрос с повторами/backoff/таймаутом.

ПараметрТипПо умолчаниюОписание
commandstringURL запроса (относительно baseURL, если задан)
reqConfigRestRequestConfigТа же конфигурация запроса, что принимают методы RestClient
retryCountnumberhttpConfig.retry.attempts ?? 0Переопределить количество повторов для этого вызова
timeoutMsnumber10000Таймаут на попытку в мс, обеспечивается через AbortController
externalSignalAbortSignalВнешний сигнал отмены (например, из 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 одновременно могут повторять запросы много экземпляров вашего приложения.

js
const executor = new RequestExecutor({
  baseURL: 'https://api.example.com',
  retry: { attempts: 5, delayMs: 200, backoffMultiplier: 2, jitterStrategy: 'full' },
})