Effect-TS patterns for Scheduling. Use when working with scheduling in Effect-TS applications.
This skill provides 3 curated Effect-TS patterns for scheduling. Use this skill when working on tasks related to:
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:
Automatic retries handle these without manual intervention.
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:
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:
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:
Without proper scheduling:
while loops wastes CPU (busy-waiting)With Schedule.fixed:
npx skills add PaulJPhilp/effect-patterns-scheduling下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer