🔁 Runs a function repeatedly until its result satisfies a condition.

Info

Waiting for something to become true: a job to leave 'pending', a container to report healthy, a file to appear, a deployment to go live. The shape is always the same — ask, check, wait, ask again — and writing it by hand means writing the timeout and the give-up path by hand each time.

Syntax

TypeScript
import { pollRun } from '@opentf/std';
pollRun<T>(
  fn: () => T | Promise<T>,
  options: PollRunOptions<T>
): Promise<T>

Parameters

  • fn: The function to poll. May be sync or async.

  • options: The polling options. until is required.

Options

  • until: Decides whether polling is done. Receives the value and the one-based attempt number, and may be async. Required.

  • interval: How long to wait between attempts, in milliseconds. Defaults to 100.

  • backoff: Whether interval stays constant or doubles each attempt — 'fixed' or 'exponential'. Defaults to 'fixed'.

  • attempts: The most attempts to make. Defaults to Infinity.

  • timeout: The longest to keep polling, in milliseconds. Defaults to Infinity.

  • signal: Aborting this signal rejects the poll and stops the pending wait.

Returns

The first result that satisfied until.

Throws

An Error if the attempts or the timeout run out first, signal.reason if the signal is aborted, and whatever fn threw, immediately.

Behavior

Info

This is the opposite of retryRun, which repeats on failure. Here a successful call whose result is not yet what was wanted is the reason to go round again, and an error is not — a poll that throws rejects immediately rather than being swallowed, since a failing call is a real failure and not a false in disguise. Compose the two where a call may fail transiently and still be worth polling: pollRun(() => retryRun(check), …).

  • The first attempt runs immediately, so a condition that already holds costs one call and no waiting.

  • timeout bounds the whole operation, not one attempt, and holds even while fn itself is running — a slow call cannot overrun it.

  • As with any timeout on a promise, the call in flight is not cancelled, because a promise has no cancel; polling simply stops. Pass signal through to the work itself where it must actually stop.

Examples

TypeScript
// Wait for a job to finish
const job = await pollRun(() => getJob(id), {
  until: (job) => job.status !== 'pending',
  interval: 1000,
  timeout: 30_000,
});
TypeScript
// Back off rather than asking at a fixed rate
await pollRun(() => ping(), {
  until: (ok) => ok,
  interval: 100,
  backoff: 'exponential',
  attempts: 8,
});
TypeScript
// The attempt number is available to the condition
await pollRun(() => check(), {
  until: (ready, attempt) => ready || attempt >= 5,
});
TypeScript
// Cancellable
const controller = new AbortController();

await pollRun(() => fetchStatus({ signal: controller.signal }), {
  until: (s) => s === 'live',
  signal: controller.signal,
});
Last updated on
Edit this page