memoize()
Cache the result of the first execution and reuse it for subsequent runs.
Syntax
typescript
task.memoize(catchErrors?: boolean): FuturableTask<T>Parameters
catchErrors (optional)
false(default): Only cache successful resultstrue: Cache both success and error results
Return Value
A new FuturableTask<T> that caches its result.
Description
The memoize() method caches the result of the first execution. Subsequent calls to run() will return the cached result without re-executing the task.
This is useful for expensive operations that should only execute once.
Examples
Basic Memoization
typescript
let counter = 0;
const task = FuturableTask
.of(() => {
console.log('Computing...');
return ++counter;
})
.memoize();
await task.run(); // Logs: "Computing...", returns 1
await task.run(); // Returns 1 (cached, no log)
await task.run(); // Returns 1 (cached, no log)Expensive API Call
typescript
const fetchConfig = FuturableTask
.fetch('/api/config')
.map(res => res.json())
.memoize();
// First call - fetches from API
const config1 = await fetchConfig.run();
// Subsequent calls - use cached result
const config2 = await fetchConfig.run();
const config3 = await fetchConfig.run();Caching Errors
typescript
let attempts = 0;
const unstableTask = FuturableTask
.of(() => {
attempts++;
if (attempts === 1) {
throw new Error('First attempt failed');
}
return 'success';
})
.memoize(false); // Don't cache errors
try {
await unstableTask.run(); // Fails
} catch (error) {
console.log('First attempt failed');
}
const result = await unstableTask.run(); // Tries again, succeedsCaching Both Success and Errors
typescript
const task = FuturableTask
.of(() => {
throw new Error('Always fails');
})
.memoize(true); // Cache errors too
try {
await task.run(); // Fails and caches the error
} catch (error) {
console.log('Failed:', error);
}
try {
await task.run(); // Returns cached error
} catch (error) {
console.log('Cached error:', error);
}Use Cases
Configuration Loading
typescript
const loadAppConfig = FuturableTask
.of(async () => {
console.log('Loading configuration...');
const res = await fetch('/api/config');
return res.json();
})
.memoize();
// Use throughout your app
const config = await loadAppConfig.run();Reference Data
typescript
const getCountries = FuturableTask
.of(() => fetch('/api/countries'))
.map(res => res.json())
.memoize();
const getLanguages = FuturableTask
.of(() => fetch('/api/languages'))
.map(res => res.json())
.memoize();
// These only fetch once
const countries = await getCountries.run();
const languages = await getLanguages.run();Expensive Computation
typescript
const computeStatistics = FuturableTask
.of(() => {
console.log('Computing statistics...');
// Complex, time-consuming calculations
return analyzeData(largeDataset);
})
.memoize();
// First call computes
const stats1 = await computeStatistics.run();
// Subsequent calls are instant
const stats2 = await computeStatistics.run();Singleton Pattern
typescript
class DatabaseConnection {
static connect = FuturableTask
.of(async () => {
console.log('Connecting to database...');
return await createConnection();
})
.memoize();
}
// Only connects once
const conn1 = await DatabaseConnection.connect.run();
const conn2 = await DatabaseConnection.connect.run(); // Same instanceBehavior Details
Caching Success Only (default)
typescript
let attempts = 0;
const task = FuturableTask
.of(() => {
attempts++;
if (attempts < 3) {
throw new Error('Failed');
}
return 'success';
})
.memoize(); // catchErrors = false
try {
await task.run(); // Fails, not cached
} catch {}
try {
await task.run(); // Fails, not cached
} catch {}
const result = await task.run(); // Succeeds, cached
const result2 = await task.run(); // Returns cached successCaching Everything
typescript
const task = FuturableTask
.of(() => {
throw new Error('Error');
})
.memoize(true); // Cache errors
try {
await task.run(); // Fails, error is cached
} catch {}
try {
await task.run(); // Returns cached error immediately
} catch {}Comparison with Non-Memoized
typescript
// Without memoization
const nonMemoized = FuturableTask.of(() => {
console.log('Executing');
return expensiveOperation();
});
await nonMemoized.run(); // Logs: "Executing"
await nonMemoized.run(); // Logs: "Executing" again
await nonMemoized.run(); // Logs: "Executing" again
// With memoization
const memoized = FuturableTask.of(() => {
console.log('Executing');
return expensiveOperation();
}).memoize();
await memoized.run(); // Logs: "Executing"
await memoized.run(); // No log (cached)
await memoized.run(); // No log (cached)Performance Benefits
typescript
// Slow operation without memoization
const slow = FuturableTask.of(() => {
let sum = 0;
for (let i = 0; i < 1000000000; i++) {
sum += i;
}
return sum;
});
console.time('Run 1');
await slow.run();
console.timeEnd('Run 1'); // ~500ms
console.time('Run 2');
await slow.run();
console.timeEnd('Run 2'); // ~500ms again
// Fast with memoization
const fast = slow.memoize();
console.time('First');
await fast.run();
console.timeEnd('First'); // ~500ms
console.time('Second');
await fast.run();
console.timeEnd('Second'); // <1ms (cached)Best Practices
1. Memoize Pure Operations
typescript
// ✅ Good - deterministic result
const getPiValue = FuturableTask
.of(() => Math.PI)
.memoize();
// ❌ Bad - non-deterministic
const getCurrentTime = FuturableTask
.of(() => Date.now())
.memoize(); // Will always return first timestamp2. Memoize Expensive Operations
typescript
// ✅ Good - expensive to compute
const parseHugeFile = FuturableTask
.of(() => parseJSON(hugeFile))
.memoize();
// ❌ Bad - cheap operation
const addNumbers = FuturableTask
.of(() => 1 + 2)
.memoize(); // Overhead not worth it3. Consider Memory Usage
typescript
// ✅ Good - small result
const getConfig = FuturableTask
.of(() => loadConfig())
.memoize();
// ⚠️ Careful - large result stays in memory
const loadEntireDatabase = FuturableTask
.of(() => fetchAllRecords())
.memoize(); // May cause memory issues4. Cache Errors Selectively
typescript
// ✅ Good - don't cache transient errors
const fetchData = FuturableTask
.fetch('/api/data')
.memoize(false); // Retry on network errors
// ✅ Good - cache validation errors
const validateInput = FuturableTask
.of(() => validate(input))
.memoize(true); // Input won't changeNotes
- Caching happens on first successful execution (or first execution if
catchErrors: true) - Cached value persists for the lifetime of the task instance
- Each task instance has its own cache
- No way to invalidate/clear the cache
- Memory is held until task is garbage collected
- Works with all other FuturableTask methods
See Also
- run() - Execute the task
- of() - Create tasks
- Concurrency Guide - Caching strategies
