
effect-patterns-scheduling
by PaulJPhilp
A community-driven knowledge base of practical patterns for Effect-TS.
SKILL.md
name: effect-patterns-scheduling description: Effect-TS patterns for Scheduling. Use when working with scheduling in Effect-TS applications.
Effect-TS Patterns: Scheduling
This skill provides 3 curated Effect-TS patterns for scheduling. Use this skill when working on tasks related to:
- scheduling
- Best practices in Effect-TS applications
- Real-world patterns and solutions
🟢 Beginner Patterns
Retry Failed Operations
Rule: Use Effect.retry with a Schedule to handle transient failures gracefully.
Good Example:
import { Effect, Schedule, Data } from "effect"
// ============================================
// 1. Define error types
// ============================================
class NetworkError extends Data.TaggedError("NetworkError")<{
readonly message: string
}> {}
class RateLimitError extends Data.TaggedError("RateLimitError")<{
readonly retryAfter: number
}> {}
class NotFoundError extends Data.TaggedError("NotFoundError")<{
readonly resource: string
}> {}
// ============================================
// 2. Simulate a flaky API call
// ============================================
let callCount = 0
const fetchData = Effect.gen(function* () {
callCount++
yield* Effect.log(`API call attempt ${callCount}`)
// Simulate intermittent failures
if (callCount < 3) {
return yield* Effect.fail(new NetworkError({ message: "Connection timeout" }))
}
return { data: "Success!", attempts: callCount }
})
// ============================================
// 3. Basic retry - fixed attempts
// ============================================
const withBasicRetry = fetchData.pipe(
Effect.retry(Schedule.recurs(5)) // Retry up to 5 times
)
// ============================================
// 4. Retry with delay
// ============================================
const withDelayedRetry = fetchData.pipe(
Effect.retry(
Schedule.spaced("500 millis").pipe(
Schedule.intersect(Schedule.recurs(5))
)
)
)
// ============================================
// 5. Retry only specific errors
// ============================================
const fetchWithErrors = (shouldFail: boolean) =>
Effect.gen(function* () {
if (shouldFail) {
// Randomly fail with different errors
const random = Math.random()
if (random < 0.5) {
return yield* Effect.fail(new NetworkError({ message: "Timeout" }))
} else if (random < 0.8) {
return yield* Effect.fail(new RateLimitError({ retryAfter: 1000 }))
} else {
return yield* Effect.fail(new NotFoundError({ resource: "user:123" }))
}
}
return "Data fetched!"
})
// Only retry network and rate limit errors, not NotFoundError
const retryTransientOnly = fetchWithErrors(true).pipe(
Effect.retry({
schedule: Schedule.recurs(3),
while: (error) =>
error._tag === "NetworkError" || error._tag === "RateLimitError",
})
)
// ============================================
// 6. Retry with exponential backoff
// ============================================
const withExponentialBackoff = fetchData.pipe(
Effect.retry(
Schedule.exponential("100 millis", 2).pipe( // 100ms, 200ms, 400ms...
Schedule.intersect(Schedule.recurs(5)) // Max 5 retries
)
)
)
// ============================================
// 7. Run and observe
// ============================================
const program = Effect.gen(function* () {
yield* Effect.log("Starting retry demo...")
// Reset counter
callCount = 0
const result = yield* withBasicRetry
yield* Effect.log(`Final result: ${JSON.stringify(result)}`)
})
Effect.runPromise(program)
Rationale:
Use Effect.retry to automatically retry operations that fail due to transient errors like network timeouts.
Many failures are temporary:
- Network issues - Connection drops, timeouts
- Rate limits - Too many requests
- Resource contention - Database locks
- Service restarts - Brief unavailability
Automatic retries handle these without manual intervention.
Your First Schedule
Rule: Use Schedule to control when and how often effects run.
Good Example:
import { Effect, Schedule } from "effect"
// ============================================
// 1. Retry a failing operation
// ============================================
let attempts = 0
const flakyOperation = Effect.gen(function* () {
attempts++
if (attempts < 3) {
yield* Effect.log(`Attempt ${attempts} failed`)
return yield* Effect.fail(new Error("Temporary failure"))
}
return `Success on attempt ${attempts}`
})
// Retry up to 5 times
const withRetry = flakyOperation.pipe(
Effect.retry(Schedule.recurs(5))
)
// ============================================
// 2. Repeat a successful operation
// ============================================
const logTime = Effect.gen(function* () {
const now = new Date().toISOString()
yield* Effect.log(`Current time: ${now}`)
return now
})
// Repeat 3 times
const repeated = logTime.pipe(
Effect.repeat(Schedule.recurs(3))
)
// ============================================
// 3. Add delays between operations
// ============================================
// Repeat every second, 5 times
const polling = logTime.pipe(
Effect.repeat(
Schedule.spaced("1 second").pipe(
Schedule.intersect(Schedule.recurs(5))
)
)
)
// ============================================
// 4. Common schedule patterns
// ============================================
// Fixed delay between attempts
const fixedDelay = Schedule.spaced("500 millis")
// Increasing delay (1s, 2s, 4s, 8s...)
const exponentialBackoff = Schedule.exponential("1 second")
// Maximum number of attempts
const limitedAttempts = Schedule.recurs(3)
// Combine: exponential backoff, max 5 attempts
const retryPolicy = Schedule.exponential("100 millis").pipe(
Schedule.intersect(Schedule.recurs(5))
)
// ============================================
// 5. Run examples
// ============================================
const program = Effect.gen(function* () {
yield* Effect.log("--- Retry Example ---")
const result = yield* withRetry
yield* Effect.log(`Result: ${result}`)
yield* Effect.log("\n--- Repeat Example ---")
yield* repeated
})
Effect.runPromise(program)
Rationale:
Use Schedule to control timing in Effect programs - retrying failed operations, repeating successful ones, or adding delays.
Schedules solve common timing problems:
- Retries - Try again after failures
- Polling - Check for updates periodically
- Rate limiting - Control how fast things run
- Backoff - Increase delays between attempts
🟡 Intermediate Patterns
Scheduling Pattern 1: Repeat an Effect on a Fixed Interval
Rule: Repeat effects at fixed intervals using Schedule.fixed for steady-state operations and background tasks.
Good Example:
This example demonstrates a health check service that polls multiple service endpoints every 30 seconds and reports their status.
import { Effect, Schedule, Duration } from "effect";
interface ServiceStatus {
readonly service: string;
readonly url: string;
readonly isHealthy: boolean;
readonly responseTime: number;
readonly lastChecked: number;
}
// Mock health check that calls an endpoint
const checkServiceHealth = (
url: string,
service: string
): Effect.Effect<ServiceStatus> =>
Effect.gen(function* () {
const startTime = Date.now();
// Simulate HTTP call with occasional failures
const isHealthy = Math.random() > 0.1; // 90% success rate
const responseTime = Math.random() * 500; // 0-500ms
yield* Effect.sleep(Duration.millis(Math.round(responseTime)));
if (!isHealthy) {
yield* Effect.fail(new Error(`${service} is unhealthy`));
}
return {
service,
url,
isHealthy: true,
responseTime: Math.round(Date.now() - startTime),
lastChecked: Date.now(),
};
});
// Health check for multiple services
interface HealthCheckConfig {
readonly services: Array<{
readonly name: string;
readonly url: string;
}>;
readonly intervalSeconds: number;
}
// Keep track of service status
const serviceStatuses = new Map<string, ServiceStatus>();
// Check all services and report status
const checkAllServices = (
config: HealthCheckConfig
): Effect.Effect<void> =>
Effect.gen(function* () {
for (const service of config.services) {
const status = yield* checkServiceHealth(service.url, service.name).pipe(
Effect.either
);
if (status._tag === "Right") {
serviceStatuses.set(service.name, status.right);
console.log(
`✓ ${service.name}: OK (${status.right.responseTime}ms)`
);
} else {
console.log(`✗ ${service.name}: FAILED`);
// Keep last known status if available
}
}
});
// Create the repeating health check
const createHealthCheckScheduler = (
config: HealthCheckConfig
): Effect.Effect<void> =>
checkAllServices(config).pipe(
// Schedule with fixed interval (fixed = ignore execution time)
Effect.repeat(
Schedule.fixed(Duration.seconds(config.intervalSeconds))
)
);
// Report current status
const reportStatus = (): Effect.Effect<void> =>
Effect.sync(() => {
if (serviceStatuses.size === 0) {
console.log("\n[STATUS] No services checked yet");
return;
}
console.log("\n[STATUS REPORT]");
for (const [service, status] of serviceStatuses) {
const ago = Math.round((Date.now() - status.lastChecked) / 1000);
console.log(
` ${service}: ${status.isHealthy ? "✓" : "✗"} (checked ${ago}s ago)`
);
}
});
// Run health checker in background and check status periodically
const program = Effect.gen(function* () {
const config: HealthCheckConfig = {
services: [
{ name: "API", url: "https://api.example.com/health" },
{ name: "Database", url: "https://db.example.com/health" },
{ name: "Cache", url: "https://cache.example.com/health" },
],
intervalSeconds: 5, // Check every 5 seconds
};
// Fork the health checker to run in background
const checker = yield* createHealthCheckScheduler(config).pipe(
Effect.fork
);
// Check and report status every 15 seconds for 60 seconds
yield* reportStatus().pipe(
Effect.repeat(
Schedule.addDelay(
Schedule.recurs(3), // 3 repetitions = 4 total (initial + 3)
() => Duration.seconds(15)
)
)
);
// Interrupt the background checker
yield* checker.interrupt();
});
Effect.runPromise(program);
This pattern:
- Defines service health checks that may fail
- Uses Schedule.fixed to repeat every 5 seconds
- Handles failures gracefully (keeps last known status)
- Runs in background while main logic continues
- Reports current status at intervals
Rationale:
When you need to run an effect repeatedly at regular intervals (e.g., every 5 seconds, every 30 minutes), use Schedule.fixed to specify the interval. This creates a schedule that repeats the effect indefinitely or until a condition stops it, with precise timing between executions.
Many production systems need periodic operations:
- Health checks: Poll service availability every 30 seconds
- Cache refresh: Update cache every 5 minutes
- Metrics collection: Gather system metrics every 10 seconds
- Data sync: Sync data with remote service periodically
- Cleanup tasks: Remove stale data nightly
Without proper scheduling:
- Manual polling with
whileloops wastes CPU (busy-waiting) - Thread.sleep blocks threads, preventing other work
- No automatic restart on failure
- Difficult to test deterministically
With Schedule.fixed:
- Efficient, non-blocking repetition
- Automatic failure handling and retry
- Testable with TestClock
- Clean, declarative syntax
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 500以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon


