Repetition
Repetition is a common requirement when working with effects in software development. It allows us to perform an effect multiple times according to a specific repetition policy.
repeat
The Effect.repeat function returns a new effect that repeats the given effect according to a specified schedule or until the first failure.
Example (Repeating a Successful Effect)
import { Effect, Schedule, Console } from "effect"
// Define an effect that logs a message to the consoleconst action = Console.log("success")
// Define a schedule that repeats the action 2 more times with a delayconst policy = Schedule.addDelay(Schedule.recurs(2), () => "100 millis")
// Repeat the action according to the scheduleconst program = Effect.repeat(action, policy)
// Run the program and log the number of repetitionsEffect.runPromise(program).then((n) => console.log(`repetitions: ${n}`))/*Output:successsuccesssuccessrepetitions: 2*/Example (Handling Failures in Repetition)
import { Effect, Schedule } from "effect"
let count = 0
// Define an async effect that simulates an action with potential failureconst action = Effect.async<string, string>((resume) => { if (count > 1) { console.log("failure") resume(Effect.fail("Uh oh!")) } else { count++ console.log("success") resume(Effect.succeed("yay!")) }})
// Define a schedule that repeats the action 2 more times with a delayconst policy = Schedule.addDelay(Schedule.recurs(2), () => "100 millis")
// Repeat the action according to the scheduleconst program = Effect.repeat(action, policy)
// Run the program and observe the result on failureEffect.runPromiseExit(program).then(console.log)/*Output:successsuccessfailure{ _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: 'Uh oh!' }}*/Skipping First Execution
If you want to avoid the first execution and only run the action according to a schedule, you can use Effect.schedule. This allows the effect to skip the initial run and follow the defined repeat policy.
Example (Skipping First Execution)
import { Effect, Schedule, Console } from "effect"
const action = Console.log("success")
const policy = Schedule.addDelay(Schedule.recurs(2), () => "100 millis")
const program = Effect.schedule(action, policy)
Effect.runPromise(program).then((n) => console.log(`repetitions: ${n}`))/*Output:successsuccessrepetitions: 2*/repeatN
The repeatN function returns a new effect that repeats the specified effect a given number of times or until the first failure. The repeats are in addition to the initial execution, so Effect.repeatN(action, 1) executes action once initially and then repeats it one additional time if it succeeds.
Example (Repeating an Action Multiple Times)
import { Effect, Console } from "effect"
const action = Console.log("success")
// Repeat the action 2 additional times after the first executionconst program = Effect.repeatN(action, 2)
Effect.runPromise(program)/*Output:successsuccesssuccess*/repeatOrElse
The repeatOrElse function returns a new effect that repeats the specified effect according to the given schedule or until the first failure.
When a failure occurs, the failure value and schedule output are passed to a specified handler.
Scheduled recurrences are in addition to the initial execution, so Effect.repeat(action, Schedule.once) executes action once initially and then repeats it an additional time if it succeeds.
Example (Handling Failure During Repeats)
import { Effect, Schedule } from "effect"
let count = 0
// Define an async effect that simulates an action with possible failuresconst action = Effect.async<string, string>((resume) => { if (count > 1) { console.log("failure") resume(Effect.fail("Uh oh!")) } else { count++ console.log("success") resume(Effect.succeed("yay!")) }})
// Define a schedule that repeats up to 2 times// with a 100ms delay between attemptsconst policy = Schedule.addDelay(Schedule.recurs(2), () => "100 millis")
// Provide a handler to run when failure occurs after the retriesconst program = Effect.repeatOrElse(action, policy, () => Effect.sync(() => { console.log("orElse") return count - 1 }),)
Effect.runPromise(program).then((n) => console.log(`repetitions: ${n}`))/*Output:successsuccessfailureorElserepetitions: 1*/Repeating Based on a Condition
You can control the repetition of an effect by a condition using either a while or until option, allowing for dynamic control based on runtime outcomes.
Example (Repeating Until a Condition is Met)
import { Effect } from "effect"
let count = 0
// Define an effect that simulates varying outcomes on each invocationconst action = Effect.sync(() => { console.log(`Action called ${++count} time(s)`) return count})
// Repeat the action until the count reaches 3const program = Effect.repeat(action, { until: (n) => n === 3 })
Effect.runFork(program)/*Output:Action called 1 time(s)Action called 2 time(s)Action called 3 time(s)*/