Caching Effects

On this page

This section outlines several functions provided by the library that help manage caching and memoization in your application:

Function NameDescription
cachedFunctionReturns a memoized version of a function with effects. Memoization ensures that results are stored and reused for the same inputs, reducing the need to recompute them.
onceReturns an effect that executes only once, regardless of how many times it's called.
cachedReturns 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.
cachedWithTTLReturns 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.
cachedInvalidateWithTTLSimilar to 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.

cachedFunction

Returns a memoized version of a function with effects. Memoization ensures that results are stored and reused for the same inputs, reducing the need to recompute them.

ts
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))
console.log(yield* randomNumber(10))
 
console.log("memoized version:")
const memoized = yield* Effect.cachedFunction(randomNumber)
console.log(yield* memoized(10))
console.log(yield* memoized(10))
})
 
Effect.runFork(program)
/*
Example Output:
non-memoized version:
2
8
memoized version:
5
5
*/
ts
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))
console.log(yield* randomNumber(10))
 
console.log("memoized version:")
const memoized = yield* Effect.cachedFunction(randomNumber)
console.log(yield* memoized(10))
console.log(yield* memoized(10))
})
 
Effect.runFork(program)
/*
Example Output:
non-memoized version:
2
8
memoized version:
5
5
*/

once

Returns an effect that executes only once, regardless of how many times it's called.

ts
import { Effect, Console } from "effect"
 
const program = Effect.gen(function* () {
const task1 = Console.log("task1")
yield* Effect.repeatN(task1, 2)
const task2 = yield* Effect.once(Console.log("task2"))
yield* Effect.repeatN(task2, 2)
})
 
Effect.runFork(program)
/*
Output:
task1
task1
task1
task2
*/
ts
import { Effect, Console } from "effect"
 
const program = Effect.gen(function* () {
const task1 = Console.log("task1")
yield* Effect.repeatN(task1, 2)
const task2 = yield* Effect.once(Console.log("task2"))
yield* Effect.repeatN(task2, 2)
})
 
Effect.runFork(program)
/*
Output:
task1
task1
task1
task2
*/

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.

ts
import { Effect, Console } from "effect"
 
let i = 1
const expensiveTask = Effect.promise<string>(() => {
console.log("expensive task...")
return new Promise((resolve) => {
setTimeout(() => {
resolve(`result ${i++}`)
}, 100)
})
})
 
const program = Effect.gen(function* () {
console.log("non-cached version:")
yield* expensiveTask.pipe(Effect.andThen(Console.log))
yield* expensiveTask.pipe(Effect.andThen(Console.log))
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 1
expensive task...
result 2
cached version:
expensive task...
result 3
result 3
*/
ts
import { Effect, Console } from "effect"
 
let i = 1
const expensiveTask = Effect.promise<string>(() => {
console.log("expensive task...")
return new Promise((resolve) => {
setTimeout(() => {
resolve(`result ${i++}`)
}, 100)
})
})
 
const program = Effect.gen(function* () {
console.log("non-cached version:")
yield* expensiveTask.pipe(Effect.andThen(Console.log))
yield* expensiveTask.pipe(Effect.andThen(Console.log))
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 1
expensive task...
result 2
cached version:
expensive task...
result 3
result 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.

ts
import { Effect, Console } from "effect"
 
let i = 1
const expensiveTask = Effect.promise<string>(() => {
console.log("expensive task...")
return new Promise((resolve) => {
setTimeout(() => {
resolve(`result ${i++}`)
}, 100)
})
})
 
const program = Effect.gen(function* () {
const cached = yield* Effect.cachedWithTTL(expensiveTask, "150 millis")
yield* cached.pipe(Effect.andThen(Console.log))
yield* cached.pipe(Effect.andThen(Console.log))
yield* Effect.sleep("100 millis")
yield* cached.pipe(Effect.andThen(Console.log))
})
 
Effect.runFork(program)
/*
Output:
expensive task...
result 1
result 1
expensive task...
result 2
*/
ts
import { Effect, Console } from "effect"
 
let i = 1
const expensiveTask = Effect.promise<string>(() => {
console.log("expensive task...")
return new Promise((resolve) => {
setTimeout(() => {
resolve(`result ${i++}`)
}, 100)
})
})
 
const program = Effect.gen(function* () {
const cached = yield* Effect.cachedWithTTL(expensiveTask, "150 millis")
yield* cached.pipe(Effect.andThen(Console.log))
yield* cached.pipe(Effect.andThen(Console.log))
yield* Effect.sleep("100 millis")
yield* cached.pipe(Effect.andThen(Console.log))
})
 
Effect.runFork(program)
/*
Output:
expensive task...
result 1
result 1
expensive task...
result 2
*/

cachedInvalidateWithTTL

Similar to 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.

ts
import { Effect, Console } from "effect"
let i = 1
const expensiveTask = Effect.promise<string>(() => {
console.log("expensive task...")
return new Promise((resolve) => {
setTimeout(() => {
resolve(`result ${i++}`)
}, 100)
})
})
const program = Effect.gen(function* () {
const [cached, invalidate] = yield* Effect.cachedInvalidateWithTTL(
expensiveTask,
"1 hour"
)
yield* cached.pipe(Effect.andThen(Console.log))
yield* cached.pipe(Effect.andThen(Console.log))
yield* invalidate
yield* cached.pipe(Effect.andThen(Console.log))
})
Effect.runFork(program)
/*
Output:
expensive task...
result 1
result 1
expensive task...
result 2
*/
ts
import { Effect, Console } from "effect"
let i = 1
const expensiveTask = Effect.promise<string>(() => {
console.log("expensive task...")
return new Promise((resolve) => {
setTimeout(() => {
resolve(`result ${i++}`)
}, 100)
})
})
const program = Effect.gen(function* () {
const [cached, invalidate] = yield* Effect.cachedInvalidateWithTTL(
expensiveTask,
"1 hour"
)
yield* cached.pipe(Effect.andThen(Console.log))
yield* cached.pipe(Effect.andThen(Console.log))
yield* invalidate
yield* cached.pipe(Effect.andThen(Console.log))
})
Effect.runFork(program)
/*
Output:
expensive task...
result 1
result 1
expensive task...
result 2
*/