mirror of
https://github.com/facebook/sapling.git
synced 2024-10-04 22:07:44 +03:00
LRU: add cachedMethod as alternative to @cached
Summary: Getting `cached` working consistently across `tsc`, `eslint`, `esbuild` (vite), `babel` (jest) seems annoying. For example, `eslint` thinks the `cached` import is unused. Let's add a non-decorator version for the same feature. Reviewed By: evangrayk Differential Revision: D54007743 fbshipit-source-id: 0d8594fec974bc485ba2d6dddca889f69a50a162
This commit is contained in:
parent
09c53005d4
commit
365e083f2d
@ -289,6 +289,25 @@ function cacheDecorator<T>(opts?: CacheOpts<T>) {
|
||||
};
|
||||
}
|
||||
|
||||
/** Cache an "instance" function like `this.foo`. */
|
||||
export function cachedMethod<T, F extends AnyFunction<T>>(originalFunc: F, opts?: CacheOpts<T>): F {
|
||||
const getExtraKeys =
|
||||
opts?.getExtraKeys ??
|
||||
function (this: T): unknown[] {
|
||||
// Use `this` as extra key if it's a value object (hash + eq).
|
||||
if (isValueObject(this)) {
|
||||
return [this];
|
||||
}
|
||||
// Scan through cachable properties.
|
||||
if (this != null && typeof this === 'object') {
|
||||
return Object.values(this).filter(isCachable);
|
||||
}
|
||||
// Give up - do not add extra cache keys.
|
||||
return [];
|
||||
};
|
||||
return cachedFunction(originalFunc, {...opts, getExtraKeys});
|
||||
}
|
||||
|
||||
const cachableTypeNames = new Set([
|
||||
'number',
|
||||
'string',
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
import type {LRUWithStats} from '../LRU';
|
||||
|
||||
import {cached, LRU, clearTrackedCache} from '../LRU';
|
||||
import {cached, LRU, clearTrackedCache, cachedMethod} from '../LRU';
|
||||
import {SelfUpdate} from '../immutableExt';
|
||||
import {List, Record} from 'immutable';
|
||||
|
||||
@ -388,3 +388,62 @@ describe('cached()', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cachedMethod', () => {
|
||||
it('is an alternative to `@cached()` decorator', () => {
|
||||
let calledTimes = 0;
|
||||
class Fib {
|
||||
fib = cachedMethod(this.fibImpl);
|
||||
fibImpl(n: number): number {
|
||||
calledTimes += 1;
|
||||
return n < 2 ? n : this.fib(n - 1) + this.fib(n - 2);
|
||||
}
|
||||
}
|
||||
const f = new Fib();
|
||||
expect(f.fib(20)).toBe(6765);
|
||||
expect(calledTimes).toBe(21);
|
||||
// Note new Fib() instance does not share the cache like the decorator version.
|
||||
const f2 = new Fib();
|
||||
expect(f2.fib(20)).toBe(6765);
|
||||
expect(calledTimes).toBe(42);
|
||||
});
|
||||
|
||||
it('supports shared cache among instances', () => {
|
||||
let calledTimes = 0;
|
||||
const cache: LRUWithStats = new LRU(10);
|
||||
class Fib {
|
||||
fib = cachedMethod(this.fibImpl, {cache});
|
||||
fibImpl(n: number): number {
|
||||
calledTimes += 1;
|
||||
return n < 2 ? n : this.fib(n - 1) + this.fib(n - 2);
|
||||
}
|
||||
}
|
||||
const f1 = new Fib();
|
||||
const f2 = new Fib();
|
||||
expect(f1.fib(20)).toBe(6765);
|
||||
expect(calledTimes).toBe(21);
|
||||
expect(f2.fib(20)).toBe(6765);
|
||||
expect(calledTimes).toBe(21); // f2 reuses f1's cache.
|
||||
});
|
||||
|
||||
it('considers state of `this`', () => {
|
||||
let calledTimes = 0;
|
||||
class Add {
|
||||
constructor(public n: number) {}
|
||||
add: (n: number) => number = cachedMethod(this.addImpl);
|
||||
addImpl(n: number): number {
|
||||
calledTimes += 1;
|
||||
return this.n + n;
|
||||
}
|
||||
}
|
||||
const a = new Add(10);
|
||||
expect(a.add(5)).toBe(15);
|
||||
expect(calledTimes).toBe(1);
|
||||
a.n = 20;
|
||||
expect(a.add(5)).toBe(25); // `this` changed
|
||||
expect(calledTimes).toBe(2);
|
||||
a.n = 10;
|
||||
expect(a.add(5)).toBe(15);
|
||||
expect(calledTimes).toBe(2); // reused cache
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user