Skip to content

Cache

In many applications, handling overlapping work is common. For example, in services that process incoming requests, it’s important to avoid redundant work like handling the same request multiple times. The Cache module helps improve performance by preventing duplicate work.

Key Features of Cache:

FeatureDescription
CompositionalityAllows overlapping work across different parts of the application while preserving compositional programming.
Unified Sync and Async CachesIntegrates both synchronous and asynchronous caches through a unified lookup function that computes values either way.
Effect IntegrationWorks natively with the Effect library, supporting concurrent lookups, failure handling, and interruption.
Cache MetricsTracks key metrics like entries, hits, and misses, providing insights for performance optimization.

A cache is defined by a lookup function that computes the value for a given key if it’s not already cached:

type Lookup<Key, Value, Error, Requirements> = (
key: Key
) => Effect<Value, Error, Requirements>

The lookup function takes a Key and returns an Effect, which describes how to compute the value (Value). This Effect may require an environment (Requirements), can fail with an Error, and succeed with a Value. Since it returns an Effect, it can handle both synchronous and asynchronous workflows.

You create a cache by providing a lookup function along with a maximum size and a time-to-live (TTL) for cached values.

declare const make: <Key, Value, Error, Requirements>(options: {
readonly capacity: number
readonly timeToLive: Duration.DurationInput
readonly lookup: Lookup<Key, Value, Error, Requirements>
}) => Effect<Cache<Key, Value, Error>, never, Requirements>

Once a cache is created, the most idiomatic way to work with it is the get method. The get method returns the current value in the cache if it exists, or computes a new value, puts it in the cache, and returns it.

If multiple concurrent processes request the same value, it will only be computed once. All other processes will receive the computed value as soon as it is available. This is managed using Effect’s fiber-based concurrency model without blocking the underlying thread.

Example (Concurrent Cache Lookups)

In this example, we call timeConsumingEffect three times concurrently with the same key. The cache runs this effect only once, so concurrent lookups will wait until the value is available:

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Cache
Cache
,
import Duration
Duration
} from "effect"
// Simulating an expensive lookup with a delay
const
const expensiveLookup: (key: string) => Effect.Effect<number, never, never>
expensiveLookup
= (
key: string
key
: string) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const sleep: (duration: Duration.DurationInput) => Effect.Effect<void>

Returns an effect that suspends for the specified duration. This method is asynchronous, and does not actually block the fiber executing the effect.

@since2.0.0

sleep
("2 seconds").
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<number, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<number, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const as: <number>(value: number) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<number, E, R> (+1 overload)

Replaces the value inside an effect with a constant value.

as allows you to ignore the original value inside an effect and replace it with a new constant value.

@example

// Title: Replacing a Value
import { pipe, Effect } from "effect"
// Replaces the value 5 with the constant "new value"
const program = pipe(Effect.succeed(5), Effect.as("new value"))
Effect.runPromise(program).then(console.log)
// Output: "new value"

@since2.0.0

as
(
key: string
key
.
String.length: number

Returns the length of a String object.

length
))
const
const program: Effect.Effect<void, never, never>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Effect.Effect<Cache.Cache<string, number, never>, never, never>> | YieldWrap<Effect.Effect<[number, number, number], never, never>> | YieldWrap<...>, void>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

@example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function* () {
// Create a cache with a capacity of 100 and an infinite TTL
const
const cache: Cache.Cache<string, number, never>
cache
= yield*
import Cache
Cache
.
const make: <string, number, never, never>(options: {
readonly capacity: number;
readonly timeToLive: Duration.DurationInput;
readonly lookup: Cache.Lookup<string, number, never, never>;
}) => Effect.Effect<...>

Constructs a new cache with the specified capacity, time to live, and lookup function.

@since2.0.0

make
({
capacity: number
capacity
: 100,
timeToLive: Duration.DurationInput
timeToLive
:
import Duration
Duration
.
const infinity: Duration.Duration

@since2.0.0

infinity
,
lookup: Cache.Lookup<string, number, never, never>
lookup
:
const expensiveLookup: (key: string) => Effect.Effect<number, never, never>
expensiveLookup
})
// Perform concurrent lookups using the same key
const
const result: [number, number, number]
result
= yield*
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const all: <readonly [Effect.Effect<number, never, never>, Effect.Effect<number, never, never>, Effect.Effect<number, never, never>], {
concurrency: "unbounded";
}>(arg: readonly [...], options?: {
concurrency: "unbounded";
} | undefined) => Effect.Effect<...>

Combines multiple effects into one, returning results based on the input structure.

When to Use

Use Effect.all when you need to run multiple effects and combine their results into a single output. It supports tuples, iterables, structs, and records, making it flexible for different input types.

For instance, if the input is a tuple:

// ┌─── a tuple of effects
// ▼
Effect.all([effect1, effect2, ...])

the effects are executed sequentially, and the result is a new effect containing the results as a tuple. The results in the tuple match the order of the effects passed to Effect.all.

Concurrency

You can control the execution order (e.g., sequential vs. concurrent) using the concurrency option.

Short-Circuiting Behavior

The Effect.all function stops execution on the first error it encounters, this is called "short-circuiting". If any effect in the collection fails, the remaining effects will not run, and the error will be propagated. To change this behavior, you can use the mode option, which allows all effects to run and collect results as Either or Option.

The mode option

The { mode: "either" } option changes the behavior of Effect.all to ensure all effects run, even if some fail. Instead of stopping on the first failure, this mode collects both successes and failures, returning an array of Either instances where each result is either a Right (success) or a Left (failure).

Similarly, the { mode: "validate" } option uses Option to indicate success or failure. Each effect returns None for success and Some with the error for failure.

@seeforEach for iterating over elements and applying an effect.

@example

// Title: Combining Effects in Tuples
import { Effect, Console } from "effect"
const tupleOfEffects = [
Effect.succeed(42).pipe(Effect.tap(Console.log)),
Effect.succeed("Hello").pipe(Effect.tap(Console.log))
] as const
// ┌─── Effect<[number, string], never, never>
// ▼
const resultsAsTuple = Effect.all(tupleOfEffects)
Effect.runPromise(resultsAsTuple).then(console.log)
// Output:
// 42
// Hello
// [ 42, 'Hello' ]

@example

// Title: Combining Effects in Iterables import { Effect, Console } from "effect"

const iterableOfEffects: Iterable<Effect.Effect> = [1, 2, 3].map( (n) => Effect.succeed(n).pipe(Effect.tap(Console.log)) )

// ┌─── Effect<number[], never, never> // ▼ const resultsAsArray = Effect.all(iterableOfEffects)

Effect.runPromise(resultsAsArray).then(console.log) // Output: // 1 // 2 // 3 // [ 1, 2, 3 ]

@example

// Title: Combining Effects in Structs import { Effect, Console } from "effect"

const structOfEffects = { a: Effect.succeed(42).pipe(Effect.tap(Console.log)), b: Effect.succeed("Hello").pipe(Effect.tap(Console.log)) }

// ┌─── Effect<{ a: number; b: string; }, never, never> // ▼ const resultsAsStruct = Effect.all(structOfEffects)

Effect.runPromise(resultsAsStruct).then(console.log) // Output: // 42 // Hello // { a: 42, b: 'Hello' }

@example

// Title: Combining Effects in Records import { Effect, Console } from "effect"

const recordOfEffects: Record<string, Effect.Effect> = { key1: Effect.succeed(1).pipe(Effect.tap(Console.log)), key2: Effect.succeed(2).pipe(Effect.tap(Console.log)) }

// ┌─── Effect<{ [x: string]: number; }, never, never> // ▼ const resultsAsRecord = Effect.all(recordOfEffects)

Effect.runPromise(resultsAsRecord).then(console.log) // Output: // 1 // 2 // { key1: 1, key2: 2 }

@example

// Title: Short-Circuiting Behavior import { Effect, Console } from "effect"

const program = Effect.all([ Effect.succeed("Task1").pipe(Effect.tap(Console.log)), Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), // Won't execute due to earlier failure Effect.succeed("Task3").pipe(Effect.tap(Console.log)) ])

Effect.runPromiseExit(program).then(console.log) // Output: // Task1 // { // _id: 'Exit', // _tag: 'Failure', // cause: { _id: 'Cause', _tag: 'Fail', failure: 'Task2: Oh no!' } // }

@example

// Title: Collecting Results with mode: "either" import { Effect, Console } from "effect"

const effects = [ Effect.succeed("Task1").pipe(Effect.tap(Console.log)), Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), Effect.succeed("Task3").pipe(Effect.tap(Console.log)) ]

const program = Effect.all(effects, { mode: "either" })

Effect.runPromiseExit(program).then(console.log) // Output: // Task1 // Task3 // { // _id: 'Exit', // _tag: 'Success', // value: [ // { _id: 'Either', _tag: 'Right', right: 'Task1' }, // { _id: 'Either', _tag: 'Left', left: 'Task2: Oh no!' }, // { _id: 'Either', _tag: 'Right', right: 'Task3' } // ] // }

@example

//Example: Collecting Results with mode: "validate" import { Effect, Console } from "effect"

const effects = [ Effect.succeed("Task1").pipe(Effect.tap(Console.log)), Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), Effect.succeed("Task3").pipe(Effect.tap(Console.log)) ]

const program = Effect.all(effects, { mode: "validate" })

Effect.runPromiseExit(program).then((result) => console.log("%o", result)) // Output: // Task1 // Task3 // { // _id: 'Exit', // _tag: 'Failure', // cause: { // _id: 'Cause', // _tag: 'Fail', // failure: [ // { _id: 'Option', _tag: 'None' }, // { _id: 'Option', _tag: 'Some', value: 'Task2: Oh no!' }, // { _id: 'Option', _tag: 'None' } // ] // } // }

@since2.0.0

all
(
[
const cache: Cache.Cache<string, number, never>
cache
.
Cache<string, number, never>.get(key: string): Effect.Effect<number, never, never>

Retrieves the value associated with the specified key if it exists. Otherwise computes the value with the lookup function, puts it in the cache, and returns it.

get
("key1"),
const cache: Cache.Cache<string, number, never>
cache
.
Cache<string, number, never>.get(key: string): Effect.Effect<number, never, never>

Retrieves the value associated with the specified key if it exists. Otherwise computes the value with the lookup function, puts it in the cache, and returns it.

get
("key1"),
const cache: Cache.Cache<string, number, never>
cache
.
Cache<string, number, never>.get(key: string): Effect.Effect<number, never, never>

Retrieves the value associated with the specified key if it exists. Otherwise computes the value with the lookup function, puts it in the cache, and returns it.

get
("key1")],
{
concurrency: "unbounded"
concurrency
: "unbounded" }
)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
"Result of parallel execution of three effects" +
`with the same key: ${
const result: [number, number, number]
result
}`
)
// Fetch and display cache stats
const
const hits: number
hits
= yield*
const cache: Cache.Cache<string, number, never>
cache
.
ConsumerCache<string, number, never>.cacheStats: Effect.Effect<Cache.CacheStats, never, never>

Returns statistics for this cache.

cacheStats
.
Pipeable.pipe<Effect.Effect<Cache.CacheStats, never, never>, Effect.Effect<number, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<Cache.CacheStats, never, never>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const map: <Cache.CacheStats, number>(f: (a: Cache.CacheStats) => number) => <E, R>(self: Effect.Effect<Cache.CacheStats, E, R>) => Effect.Effect<number, E, R> (+1 overload)

Transforms the value inside an effect by applying a function to it.

Syntax

const mappedEffect = pipe(myEffect, Effect.map(transformation))
// or
const mappedEffect = Effect.map(myEffect, transformation)
// or
const mappedEffect = myEffect.pipe(Effect.map(transformation))

Details

map takes a function and applies it to the value contained within an effect, creating a new effect with the transformed value.

It's important to note that effects are immutable, meaning that the original effect is not modified. Instead, a new effect is returned with the updated value.

@seemapError for a version that operates on the error channel.

@seemapBoth for a version that operates on both channels.

@seeflatMap or andThen for a version that can return a new effect.

@example

// Title: Adding a Service Charge
import { pipe, Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const finalAmount = pipe(
fetchTransactionAmount,
Effect.map(addServiceCharge)
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 101

@since2.0.0

map
((
stats: Cache.CacheStats
stats
) =>
stats: Cache.CacheStats
stats
.
CacheStats.hits: number
hits
)
)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(`Number of cache hits: ${
const hits: number
hits
}`)
const
const misses: number
misses
= yield*
const cache: Cache.Cache<string, number, never>
cache
.
ConsumerCache<string, number, never>.cacheStats: Effect.Effect<Cache.CacheStats, never, never>

Returns statistics for this cache.

cacheStats
.
Pipeable.pipe<Effect.Effect<Cache.CacheStats, never, never>, Effect.Effect<number, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<Cache.CacheStats, never, never>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const map: <Cache.CacheStats, number>(f: (a: Cache.CacheStats) => number) => <E, R>(self: Effect.Effect<Cache.CacheStats, E, R>) => Effect.Effect<number, E, R> (+1 overload)

Transforms the value inside an effect by applying a function to it.

Syntax

const mappedEffect = pipe(myEffect, Effect.map(transformation))
// or
const mappedEffect = Effect.map(myEffect, transformation)
// or
const mappedEffect = myEffect.pipe(Effect.map(transformation))

Details

map takes a function and applies it to the value contained within an effect, creating a new effect with the transformed value.

It's important to note that effects are immutable, meaning that the original effect is not modified. Instead, a new effect is returned with the updated value.

@seemapError for a version that operates on the error channel.

@seemapBoth for a version that operates on both channels.

@seeflatMap or andThen for a version that can return a new effect.

@example

// Title: Adding a Service Charge
import { pipe, Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const finalAmount = pipe(
fetchTransactionAmount,
Effect.map(addServiceCharge)
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 101

@since2.0.0

map
((
stats: Cache.CacheStats
stats
) =>
stats: Cache.CacheStats
stats
.
CacheStats.misses: number
misses
)
)
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(`Number of cache misses: ${
const misses: number
misses
}`)
})
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const runPromise: <void, never>(effect: Effect.Effect<void, never, never>, options?: {
readonly signal?: AbortSignal;
} | undefined) => Promise<void>

Executes an effect and returns the result as a Promise.

When to Use

Use runPromise when you need to execute an effect and work with the result using Promise syntax, typically for compatibility with other promise-based code.

If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.

@seerunPromiseExit for a version that returns an Exit type instead of rejecting.

@example

// Title: Running a Successful Effect as a Promise
import { Effect } from "effect"
Effect.runPromise(Effect.succeed(1)).then(console.log)
// Output: 1

@example

//Example: Handling a Failing Effect as a Rejected Promise import { Effect } from "effect"

Effect.runPromise(Effect.fail("my error")).catch(console.error) // Output: // (FiberFailure) Error: my error

@since2.0.0

runPromise
(
const program: Effect.Effect<void, never, never>
program
)
/*
Output:
Result of parallel execution of three effects with the same key: 4,4,4
Number of cache hits: 2
Number of cache misses: 1
*/

The cache is designed to be safe for concurrent access and efficient under concurrent conditions. If two concurrent processes request the same value and it is not in the cache, the value will be computed once and provided to both processes as soon as it is available. Concurrent processes will wait for the value without blocking the underlying thread.

If the lookup function fails or is interrupted, the error will be propagated to all concurrent processes waiting for the value. Failures are cached to prevent repeated computation of the same failed value. If interrupted, the key will be removed from the cache, so subsequent calls will attempt to compute the value again.

A cache is created with a specified capacity. When the cache reaches capacity, the least recently accessed values will be removed first. The cache size may slightly exceed the specified capacity between operations.

A cache can also have a specified time to live (TTL). Values older than the TTL will not be returned. The age is calculated from when the value was loaded into the cache.

In addition to get, the cache provides several other methods:

MethodDescription
refreshTriggers a recomputation of the value for a key without removing the old value, allowing continued access.
sizeReturns the current size of the cache. The size is approximate under concurrent conditions.
containsChecks if a value associated with a specified key exists in the cache. Under concurrent access, the result is valid as of the check time but may change immediately after.
invalidateEvicts the value associated with a specific key.
invalidateAllEvicts all values from the cache.