Retry a Failed Operation with Effect.retry
Guideline
Use Effect.retry to automatically retry an Effect that fails. Combine it
with a Schedule to control how many times to retry and how long to wait
between attempts.
Rationale
Network requests fail. Databases time out. Services go down temporarily. Instead of failing immediately, you often want to retry a few times. Effect makes this a one-liner.
Simple Retry
import { Effect, Schedule } from "effect";
// An operation that might fail
const fetchData = Effect.tryPromise({
try: () => fetch("https://api.example.com/data"),
catch: () => new Error("Network error"),
});
// Retry up to 3 times
const withRetry = Effect.retry(fetchData, Schedule.recurs(3));
Retry with Delay
import { Effect, Schedule } from "effect";
const unreliableOperation = Effect.gen(function* () {
const random = Math.random();
if (random < 0.7) {
return yield* Effect.fail("Random failure");
}
return "Success!";
});
// Retry up to 3 times, waiting 1 second between attempts
const withDelay = Effect.retry(
unreliableOperation,
Schedule.recurs(3).pipe(Schedule.addDelay(() => "1 second"))
);
Common Retry Patterns
import { Effect, Schedule } from "effect";
const operation = Effect.fail("temporary error");
// Retry 5 times immediately
const quick = Effect.retry(operation, Schedule.recurs(5));
// Retry 3 times with 1 second between
const spaced = Effect.retry(
operation,
Schedule.spaced("1 second").pipe(Schedule.intersect(Schedule.recurs(3)))
);
// Retry with exponential backoff (1s, 2s, 4s, 8s...)
const exponential = Effect.retry(
operation,
Schedule.exponential("1 second").pipe(Schedule.intersect(Schedule.recurs(5)))
);
Good Example: Retrying an API Call
import { Effect, Schedule, pipe } from "effect";
class ApiError {
readonly _tag = "ApiError";
constructor(readonly status: number) {}
}
const fetchUserData = (userId: string) =>
Effect.tryPromise({
try: async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new ApiError(response.status);
return response.json();
},
catch: (error) => error as ApiError,
});
// Retry up to 3 times with 500ms between attempts
const fetchWithRetry = (userId: string) =>
pipe(
fetchUserData(userId),
Effect.retry(
Schedule.recurs(3).pipe(Schedule.addDelay(() => "500 millis"))
),
Effect.catchAll((error) =>
Effect.succeed({ error: `Failed after retries: ${error._tag}` })
)
);
Quick Reference
| Schedule | Behavior |
|---|---|
Schedule.recurs(3) |
Retry 3 times immediately |
Schedule.spaced("1 second") |
Retry forever with 1s delay |
Schedule.exponential("100 millis") |
Exponential backoff |
Schedule.forever |
Retry indefinitely |
Key Points
- Effect.retry takes an Effect and a Schedule
- Schedule controls the retry timing and count
- Combine schedules with
.pipe()for complex behavior - Still need error handling - retries might all fail
What's Next?
- Learn exponential backoff for production systems
- Learn Schedule combinators for complex retry logic
- Learn about circuit breakers for failing services