Schedule
Retry and repeat with backoff strategies
Retry and repeat with backoff strategies. Resilient by default.
The problem
Network calls fail, APIs rate-limit, and databases experience transient errors. Writing correct retry logic by hand is error-prone:
async function fetchWithRetry(url: string) {
for (let i = 0; i < 3; i++) {
try {
return await fetch(url)
} catch (e) {
if (i === 2) throw e
await new Promise(r => setTimeout(r, Math.pow(2, i) * 100))
}
}
}This approach does not compose. Every function that needs retries requires its own retry loop, and adding jitter, max duration, or logging means rewriting each one.
The solution
Schedule.retry works with Result — your function returns a Result, and retry checks !result.ok to decide whether to try again:
import { Schedule, Result } from "within-ts"
// callApi returns Result<Data, ApiError>
// Simple retry — retries on any Result.err
const result = await Schedule.retry(() => callApi(), {
times: 3,
delay: "100ms",
})
// Exponential backoff
const result = await Schedule.retry(() => callApi(), {
times: 5,
delay: Schedule.exponential("100ms"), // 100, 200, 400, 800, 1600
})
// Exponential with jitter and max delay
const result = await Schedule.retry(() => callApi(), {
times: 5,
delay: Schedule.exponential("100ms", { jitter: true, max: "5s" }),
})
// Retry only specific errors — while receives the unwrapped error
const result = await Schedule.retry(() => callApi(), {
times: 3,
delay: "1s",
while: (error) => error instanceof RateLimitError,
})The flow:
- Call the function
- If it returns
Result.ok— done, return it - If it returns
Result.err— pass the error towhile(if provided) - If
whilereturnstrue(or nowhile) — wait, retry - If
whilereturnsfalseor retries exhausted — return the lastResult.err
No exceptions are thrown at any point. The entire flow stays within the Result type.