Skip to content
Prose v0.3.2

Retries

Chain .withRetry() after any step to add a retry policy. This is ideal for steps that call external APIs or services where transient failures are expected.

flow
.step('callExternalApi', async (ctx) => {
const data = await api.fetch(ctx.input.url);
return { data };
})
.withRetry({
maxAttempts: 5,
delayMs: 100,
backoffMultiplier: 2,
maxDelayMs: 5_000,
shouldRetry: (err) => err.status !== 400,
stepTimeout: 10_000,
})
OptionTypeDefaultDescription
maxAttemptsnumberTotal attempts including the first
delayMsnumberInitial delay between retries (ms)
backoffMultipliernumberMultiplier applied to delay after each retry. Without this, the delay is fixed.
maxDelayMsnumberUpper bound on delay
shouldRetry(error) => booleanPredicate to conditionally retry
stepTimeoutnumberTimeout override for this step (ms)

With backoffMultiplier: 2 and delayMs: 100, retries wait 100ms, 200ms, 400ms, 800ms, etc. Use maxDelayMs to cap the delay.

.withRetry({
maxAttempts: 5,
delayMs: 100,
backoffMultiplier: 2,
maxDelayMs: 5_000, // never wait more than 5 seconds
})

Use shouldRetry to skip retries for non-transient errors like validation failures or 4xx HTTP responses.

.withRetry({
maxAttempts: 3,
delayMs: 500,
shouldRetry: (err) => {
// Don't retry client errors
if (err.status >= 400 && err.status < 500) return false;
return true;
},
})

When using an observer, the onStepRetry hook is called before each retry attempt:

await flow.execute(input, deps, {
observer: {
onStepRetry: (stepName, attempt, maxAttempts, error) => {
console.log(`Retrying ${stepName}: attempt ${attempt}/${maxAttempts}`);
},
},
});

Steps added with .validate() are never retried, even if .withRetry() is chained after them. Validation failures should fail fast.