Caching Effects
This section covers several functions from the library that help manage caching and memoization in your application.
cachedFunction
Memoizes a function with effects, caching results for the same inputs to avoid recomputation.
Example (Memoizing a Random Number Generator)
import { Effect, Random } from "effect"
const program = Effect.gen(function* () { const randomNumber = (n: number) => Random.nextIntBetween(1, n) console.log("non-memoized version:") console.log(yield* randomNumber(10)) // Generates a new random number console.log(yield* randomNumber(10)) // Generates a different number
console.log("memoized version:") const memoized = yield* Effect.cachedFunction(randomNumber) console.log(yield* memoized(10)) // Generates and caches the result console.log(yield* memoized(10)) // Reuses the cached result})
Effect.runFork(program)/*Example Output:non-memoized version:28memoized version:55*/once
Ensures an effect is executed only once, even if invoked multiple times.
Example (Single Execution of an Effect)
import { Effect, Console } from "effect"
const program = Effect.gen(function* () { const task1 = Console.log("task1")
// Repeats task1 three times yield* Effect.repeatN(task1, 2)
// Ensures task2 is executed only once const task2 = yield* Effect.once(Console.log("task2"))
// Attempts to repeat task2, but it will only execute once yield* Effect.repeatN(task2, 2)})
Effect.runFork(program)/*Output:task1task1task1task2*/cached
Returns an effect that computes a result lazily and caches it. Subsequent evaluations of this effect will return the cached result without re-executing the logic.
Example (Lazy Caching of an Expensive Task)
import { Effect, Console } from "effect"
let i = 1
// Simulating an expensive task with a delayconst expensiveTask = Effect.promise<string>(() => { console.log("expensive task...") return new Promise((resolve) => { setTimeout(() => { resolve(`result ${i++}`) }, 100) })})
const program = Effect.gen(function* () { // Without caching, the task is executed each time console.log("-- non-cached version:") yield* expensiveTask.pipe(Effect.andThen(Console.log)) yield* expensiveTask.pipe(Effect.andThen(Console.log))
// With caching, the result is reused after the first run console.log("-- cached version:") const cached = yield* Effect.cached(expensiveTask) yield* cached.pipe(Effect.andThen(Console.log)) yield* cached.pipe(Effect.andThen(Console.log))})
Effect.runFork(program)/*Output:-- non-cached version:expensive task...result 1expensive task...result 2-- cached version:expensive task...result 3result 3*/cachedWithTTL
Returns an effect that caches its result for a specified duration, known as the timeToLive. When the cache expires after the duration, the effect will be recomputed upon next evaluation.
Example (Caching with Time-to-Live)
import { Effect, Console } from "effect"
let i = 1
// Simulating an expensive task with a delayconst expensiveTask = Effect.promise<string>(() => { console.log("expensive task...") return new Promise((resolve) => { setTimeout(() => { resolve(`result ${i++}`) }, 100) })})
const program = Effect.gen(function* () { // Caches the result for 150 milliseconds const cached = yield* Effect.cachedWithTTL(expensiveTask, "150 millis")
// First evaluation triggers the task yield* cached.pipe(Effect.andThen(Console.log))
// Second evaluation returns the cached result yield* cached.pipe(Effect.andThen(Console.log))
// Wait for 100 milliseconds, ensuring the cache expires yield* Effect.sleep("100 millis")
// Recomputes the task after cache expiration yield* cached.pipe(Effect.andThen(Console.log))})
Effect.runFork(program)/*Output:expensive task...result 1result 1expensive task...result 2*/cachedInvalidateWithTTL
Similar to Effect.cachedWithTTL, this function caches an effect’s result for a specified duration. It also includes an additional effect for manually invalidating the cached value before it naturally expires.
Example (Invalidating Cache Manually)
import { Effect, Console } from "effect"
let i = 1
// Simulating an expensive task with a delayconst expensiveTask = Effect.promise<string>(() => { console.log("expensive task...") return new Promise((resolve) => { setTimeout(() => { resolve(`result ${i++}`) }, 100) })})
const program = Effect.gen(function* () { // Caches the result for 150 milliseconds const [cached, invalidate] = yield* Effect.cachedInvalidateWithTTL(expensiveTask, "150 millis")
// First evaluation triggers the task yield* cached.pipe(Effect.andThen(Console.log))
// Second evaluation returns the cached result yield* cached.pipe(Effect.andThen(Console.log))
// Invalidate the cache before it naturally expires yield* invalidate
// Third evaluation triggers the task again // since the cache was invalidated yield* cached.pipe(Effect.andThen(Console.log))})
Effect.runFork(program)/*Output:expensive task...result 1result 1expensive task...result 2*/