There are cases where you want to execute heavy initialization only on first access, then reuse the generated value thereafter. This pattern achieves this simply, without the overhead of a memory cache.
Code
export interface Reusable<T> { value: T;}
export function reusable<T>(init: () => T): Reusable<T> { return { get value() { const value = init(); Object.defineProperty(this, "value", { value }); return value; }, };}Usage Examples
Fixed Timestamp
const timestamp = reusable(() => new Date());
// Date is generated on first accessconsole.log(timestamp.value); // 2025-01-18T09:00:00.000Z
// Same value returned on subsequent accessesconsole.log(timestamp.value); // 2025-01-18T09:00:00.000ZHeavy Configuration Loading
const config = reusable(() => { // File loading, environment variable parsing, etc. return loadConfigFromFile();});
// Configuration is loaded only when neededfunction processData() { const settings = config.value; // Loading executes only on first access // ...}How It Works
- Executes the init function on first getter access
- Overwrites value as a data property using Object.defineProperty
- On subsequent accesses, the getter is not called and the value is returned directly
This ensures initialization cost only occurs on first access, with performance equivalent to regular property access thereafter.
Comparison with Memory Caching
Differences from memory caches (like Map):
- This pattern is specialized for single values
- No key management like Map required
- Garbage collection behaves the same as regular objects
Conversely, use Map or WeakMap when you want to cache multiple values.
Caveats
Once initialized, the value never changes. To reinitialize, you must create a new reusable instance. Also, if the init function throws an exception, the same exception will be re-thrown on subsequent accesses.
hsb.horse