@exadel/esl
Version:
Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components
128 lines (106 loc) • 4.08 kB
Markdown
# @memoize Decorator
Caches method return values or getter value to avoid recomputation. Works for instance (prototype) and static members.
## Why
Expensive pure (or effectively pure) calculations and derived state accessors are common in component logic. Re-computing them on every call wastes CPU cycles and may create unnecessary garbage allocations. Manual caching patterns add boilerplate and are easy to get wrong or forget to invalidate.
`@memoize` provides:
- Zero-dependency, lightweight memoization.
- Lazy per‑instance installation (prototype members) for isolation.
- Explicit cache inspection & reset helpers.
## Quick Start
```ts
import {memoize} from '@exadel/esl/modules/esl-utils/decorators';
class Parser {
raw: string;
constructor(raw: string) { this.raw = raw; }
// Getter memoized (value cached after first access)
@memoize()
get ast() { return heavyParse(this.raw); }
// Method memoized by first primitive argument only (default hash behavior)
@memoize()
classify(token: string) { return classifyToken(token); }
}
const p = new Parser(source);
const a1 = p.ast; // computes
const a2 = p.ast; // cached value
p.classify('id'); // computes & caches under key 'id'
p.classify('id'); // cached
```
## API
```ts
memoize(): MethodDecorator;
memoize(hashFn: MemoHashFn): MethodDecorator;
```
Where `MemoHashFn` returns `string | null | undefined`:
- `string | null` => used as cache key (`null` is a valid key)
- `undefined` => skip caching for that invocation
### Attached Helpers
```ts
memoize.clear(target, prop | prop[]);
memoize.has(target, prop, ...args?);
```
## Behavior Details
| Member Kind | What Happens | Cache Scope |
|-------------|--------------|-------------|
| Prototype getter | First access computes value, stores it as an own value property (descriptor replaced). | Per instance (value) |
| Prototype method | First call replaces method on that instance with a memoized wrapper function. | Per instance (function + its Map) |
| Static getter/method | Replaced by shared memoized function at class level. | Shared (class level) |
Hash function drives cache key. Default hash (`defaultArgsHashFn`):
- 0 args => key `null`
- 1 primitive (string/number/boolean) arg => its stringified value
- Otherwise => returns `undefined` (no caching)
Underlying memoized function surface:
```ts
fn.cache // Map<null | string, ReturnType>
fn.clear() // empties cache
fn.has(...args) // true if key present (args hashed)
```
## Custom Hash Examples
### Multiple Params
```ts
const hash = (a: string, b: number) => a + '|' + b;
class C { @memoize(hash) mix(a: string, b: number) { /* ... */ } }
```
### Conditional Skip
```ts
const hash = (query: string) => query.length > 1000 ? undefined : query;
```
(Calls with very long queries skip caching.)
## Cache Control
```ts
memoize.has(instance, 'classify', 'id'); // check by hashed args
memoize.clear(instance, 'classify'); // remove function or underlying cache
memoize.clear(instance, ['ast', 'classify']); // batch
```
For getters, clearing deletes the own value property (next access recomputes). For methods, clearing removes the installed memoized function (next call reinstalls).
## Examples
### Getter + Method Combined
```ts
class Store {
data: Item[] = [];
@memoize()
get ids() { return this.data.map(i => i.id); }
@memoize(args => args[0] ?? undefined)
findById(id: string) { return this.data.find(i => i.id === id); }
}
```
### Async Function
```ts
class Service {
@memoize()
async fetchOne(id: string) { return (await api.get(id)).payload; }
}
```
Resulting Promise is cached; rejection stays cached, so clear before retry if needed.
## Best Practices
- Use only for deterministic / pure (per instance) computations.
- Keep hash functions fast—avoid JSON.stringify on large objects unless necessary.
- Clear caches when underlying mutable state changes (e.g. after updating internal data arrays).
- Be careful with async methods: decide whether caching in-flight Promises is desired.