Skip to content

RequestExecutor

Lower-level wrapper for a single REST request with retry, timeout (via AbortController), Retry-After header support, and backoff. This is the class createRestClient() uses internally for retry — use it directly when you want retry/backoff without the rest of RestClient (caching, rate limiting, auth, …).

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, // cap Retry-After at 30 s
  },
})

// 5th arg: external AbortSignal (e.g. from orchestrator.abort())
const res = await executor.execute('/data', undefined, 3, 5000, signal)

When the server returns a Retry-After header (numeric seconds or HTTP-date), that delay takes priority over the backoff formula. Values exceeding maxRetryAfterMs are clamped to the cap. Timeout is enforced via AbortController — the actual HTTP request is cancelled, not just the promise.

Constructor

ts
new RequestExecutor(httpConfig: HttpConfig)

Takes the same HttpConfig as createRestClient() — in practice, RequestExecutor itself only reads httpConfig.retry and uses the rest to construct its underlying HTTP client.

Methods

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

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

Executes a single request with retry/backoff/timeout.

ParameterTypeDefaultDescription
commandstringRequest URL (relative to baseURL, if set)
reqConfigRestRequestConfigSame request config accepted by RestClient's methods
retryCountnumberhttpConfig.retry.attempts ?? 0Override the number of retry attempts for this call
timeoutMsnumber10000Per-attempt timeout in ms, enforced via AbortController
externalSignalAbortSignalAn outside abort signal to cancel the request (e.g. orchestrator.abort()'s signal)

Jitter strategies

retry.jitterStrategy controls how randomness is added to the computed backoff delay (it doesn't affect Retry-After, which is always used as-is):

  • "fixed" (default) — delayMs * backoffMultiplier^(attempt-1) plus up to +10% random jitter on top. Backward compatible; the delay never falls below the pure backoff value.
  • "full"delay = random(0, delayMs * backoffMultiplier^(attempt-1)). Better at spreading out many concurrent retriers (avoids synchronized retry storms hitting the backend at the same instant), at the cost of individual delays sometimes being much shorter than the nominal backoff.
  • "decorrelated"delay = min(cap, random(delayMs, prevDelay * 3)), where prevDelay starts at delayMs and updates after each attempt, and cap is delayMs * backoffMultiplier^attempts. Spreads out concurrent retriers even better than "full" since each client's next delay depends on its own previous one.

Both algorithms are from AWS's Exponential Backoff and Jitter post — use them when many instances of your app may retry against the same backend at once.

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