TypeScript Method Memoization Decorator
typescript-memoize is a decorator library for TypeScript that enables method and getter memoization, primarily for optimizing performance by caching the results of expensive operations. As of version 1.1.1, it provides `@Memoize` for standard caching and `@MemoizeExpiring` for time-limited caching, which automatically invalidates cached values after a specified duration. The library offers flexibility in how memoization keys are generated, supporting methods without parameters, `get` accessors, and methods where memoization is based on specific parameters or custom hash functions. It's a stable library, though release cadence is not explicitly defined, new versions appear infrequently. Its key differentiator lies in its decorator-based approach, integrating seamlessly with TypeScript classes, and allowing for granular control over cache invalidation via custom hash functions or expiration.
Common errors
-
TypeError: Cannot read properties of undefined (reading '__metadata')
cause TypeScript compiler options for decorators are not correctly configured, often missing `emitDecoratorMetadata`.fixAdd or ensure `"experimentalDecorators": true` and `"emitDecoratorMetadata": true` in the `compilerOptions` section of your `tsconfig.json`. -
TypeError: (0 , typescript_memoize_1.Memoize) is not a function
cause Attempting to import or use the decorator with CommonJS `require()` syntax or incorrect destructuring in an ES Module context.fixUse ES Module import syntax: `import { Memoize } from 'typescript-memoize';`
Warnings
- gotcha TypeScript decorators require specific compiler options (`"experimentalDecorators": true` and usually `"emitDecoratorMetadata": true`) in `tsconfig.json` to function correctly.
- gotcha When `@Memoize()` is applied to a method with multiple parameters *without* a custom hash function, it will by default only use the *first* parameter for memoization key generation. Subsequent parameters are ignored for caching purposes, potentially leading to incorrect cached values if the method's result depends on later parameters.
- gotcha The library does not provide a public API for manual cache invalidation beyond the `MemoizeExpiring` decorator. If you need to clear the cache for a non-expiring memoized method dynamically, you would need to recreate the class instance or implement custom cache invalidation logic.
Install
-
npm install typescript-memoize -
yarn add typescript-memoize -
pnpm add typescript-memoize
Imports
- Memoize
const Memoize = require('typescript-memoize');import { Memoize } from 'typescript-memoize'; - MemoizeExpiring
import MemoizeExpiring from 'typescript-memoize';
import { MemoizeExpiring } from 'typescript-memoize'; - Custom Hash Function
@Memoize((param1: any, param2: any) => `${param1}-${param2}`)
Quickstart
import { Memoize, MemoizeExpiring } from 'typescript-memoize';
class DataService {
private callCount: number = 0;
@Memoize()
public getExpensiveData(): string {
this.callCount++;
console.log(`Fetching expensive data... (call count: ${this.callCount})`);
return `Data fetched at ${new Date().toISOString()}`;
}
@MemoizeExpiring(3000) // Cache expires after 3 seconds
public getExpiringData(param: string): string {
console.log(`Fetching expiring data for '${param}'...`);
return `Expiring data for ${param} at ${new Date().toISOString()}`;
}
// Custom hash function for memoizing based on multiple parameters
@Memoize((id: number, type: string) => `${id}-${type}`)
public getItem(id: number, type: string): string {
console.log(`Fetching item ${id} of type ${type}...`);
return `Item ${id} (${type}) retrieved at ${new Date().toISOString()}`;
}
}
async function runExample() {
const service = new DataService();
console.log("--- Standard Memoize ---");
console.log(service.getExpensiveData()); // Fetches
console.log(service.getExpensiveData()); // Returns cached
console.log(service.getExpensiveData()); // Returns cached
console.log("\n--- Expiring Memoize ---");
console.log(service.getExpiringData("A")); // Fetches
console.log(service.getExpiringData("A")); // Returns cached
await new Promise(resolve => setTimeout(resolve, 3100)); // Wait for cache to expire
console.log(service.getExpiringData("A")); // Fetches again after expiration
console.log("\n--- Custom Hash Function Memoize ---");
console.log(service.getItem(1, "book")); // Fetches
console.log(service.getItem(1, "book")); // Returns cached
console.log(service.getItem(2, "book")); // Different key, fetches
console.log(service.getItem(1, "movie")); // Different key, fetches
}
runExample();