🔁 Runs a function repeatedly until its result satisfies a condition.
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
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.untilis 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 to100.backoff: Whetherintervalstays constant or doubles each attempt —'fixed'or'exponential'. Defaults to'fixed'.attempts: The most attempts to make. Defaults toInfinity.timeout: The longest to keep polling, in milliseconds. Defaults toInfinity.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
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.
timeoutbounds the whole operation, not one attempt, and holds even whilefnitself 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
signalthrough to the work itself where it must actually stop.
Examples
// Wait for a job to finish const job = await pollRun(() => getJob(id), { until: (job) => job.status !== 'pending', interval: 1000, timeout: 30_000, });
// Back off rather than asking at a fixed rate await pollRun(() => ping(), { until: (ok) => ok, interval: 100, backoff: 'exponential', attempts: 8, });
// The attempt number is available to the condition await pollRun(() => check(), { until: (ready, attempt) => ready || attempt >= 5, });
// Cancellable const controller = new AbortController(); await pollRun(() => fetchStatus({ signal: controller.signal }), { until: (s) => s === 'live', signal: controller.signal, });