Retries

YieldStar implements structured retries through RetryableError. Throwing this error inside a step.run signals to the runtime that the step should be re-executed according to the specified retry policy.

Basic retry

import { RetryableError } from "yieldstar";

yield* step.run(async () => {
  const res = await fetch("https://api.example.com/charge");
  if (!res.ok) {
    throw new RetryableError("Payment failed", {
      maxAttempts: 5,
      retryInterval: 2000,
    });
  }
  return res.json();
});
OptionTypeDescription
maxAttemptsnumberTotal number of times the step executes (including the first attempt)
retryIntervalnumberMilliseconds between retry attempts

How retries work

When a RetryableError is thrown:

  1. The runtime caches the error with done: false, indicating the step is incomplete.
  2. The runtime schedules a wake-up after retryInterval milliseconds.
  3. The worker process terminates.
  4. On the next invocation, the runtime replays cached steps, reaches the incomplete step, clears the cached error, and re-executes the function.
  5. This repeats until the function succeeds or maxAttempts is reached.

When maxAttempts is exhausted, the error is thrown into the workflow generator. You can catch it with a standard try/catch:

try {
  yield* step.run(async () => {
    throw new RetryableError("flaky", { maxAttempts: 3, retryInterval: 1000 });
  });
} catch (err) {
  // all 3 attempts failed
  yield* step.run(() => markAsFailed());
}

Non-retryable errors

Any error that is not a RetryableError terminates the step immediately and throws into the workflow — no retries, no scheduling. The error is cached with done: true.

yield* step.run(() => {
  throw new Error("fatal"); // no retry, thrown immediately
});

Retry via polling

step.poll is a higher-level primitive that uses RetryableError internally. It retries a predicate at a fixed interval:

yield* step.poll(
  { retryInterval: 1000, maxAttempts: 10 },
  () => isReady()
);

If isReady() returns false, the poll throws a RetryableError with the configured policy. If it returns true, the step completes. If it throws a regular error, the step fails without retrying.