Skip to content

Instantly share code, notes, and snippets.

@jmendiara
Last active December 14, 2020 14:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmendiara/82e9926d539bea807e5c7566f836ee17 to your computer and use it in GitHub Desktop.
Save jmendiara/82e9926d539bea807e5c7566f836ee17 to your computer and use it in GitHub Desktop.
LRU Cache for Promises in typescript
import LRU from 'lru-cache';
/**
* Small utility around an LRU cache that gives us some features:
* - Promisification: Async ready to easily change to a memcached/redis implementation
* - Improved get method, to make more expressive usage of the pattern "if not found
* in the cache, go get it, store in the cache, and return the value"
* - Funnel values: Promises are stored and returned, so if the value for a key
* is being obtained while another get is requested, the promise of the value is returned
* so only one request for value is done
*/
export class Cache<K, V = unknown> {
lru: LRU<K, Promise<V>>;
constructor(opt: LRU.Options<K, Promise<V>>) {
this.lru = new LRU(opt);
}
/**
* Gets a value from the cache. When an optional getter method is provided,
* it will be called when there is a cache miss to get the value and store
* it in the cache.
* When the getter method throws/rejects, it will be propagated down the chain
*
* ```js
* // Old Way (Sync code)
* let value = cache.get(key);
* if (!value) {
* value = calculateValue();
* cache.put(key, value);
* }
* return value;
* ```
* that becomes
* ```js
* return cache.get(key, calculateValue);
* ```
*
* @param {String} key the cache entry key
* @param {Function} [getter] the function to call when a value is not found
* in the cache. Return promise or a discrete value, that will be
* stored in the cache for that key
* @returns {Promise.<V>} the cache entry
*/
async get(key: K, getter: () => Promise<V>, maxAge?: number): Promise<V> {
const inCache = this.lru.get(key);
if (inCache) {
return inCache;
}
// Create a promise to hold the getter promise,
// because it may throw sync
const promise = new Promise<V>((resolve, reject) => {
try {
resolve(getter());
} catch (err) {
reject(err);
}
});
// once stored, we resolve with the getter promise,
// to allow userland to use the value inmediatly
await this.set(key, promise, maxAge);
return promise;
}
/**
* Sets a value in the cache
*
* When the value is a promise, and the value rejects, it will be automatically
* dropped from the cache
*
* @returns {Promise.<Boolean>} Success on setting the value on the cache
*/
async set(key: K, value: Promise<V>, maxAge?: number): Promise<boolean> {
// catch its error and delete from cache
Promise.resolve(value).catch((err) => this.del(key));
return this.lru.set(key, value, maxAge);
}
/**
* Deletes a value from the cache
*
* @param {String} key
* @returns {Promise.<*>}
*/
async del(key: K): Promise<void> {
return this.lru.del(key);
}
/**
* Clear the cache entirely, throwing away all values.
*
* @returns {Promise.<*>}
*/
async reset(): Promise<void> {
return this.lru.reset();
}
}
import { Cache } from './cache';
const cache = new Cache<string, Car>({
max: 50, // # of items
maxAge: 10 * 60 * 1000, // expiration in ms (10 min)
});
// The old method without cache
const getCar = async (name: string) => {
const { data } = await axios.get<Car>(`https://example.com/cars/${name}`);
return data;
});
// With cache: easy addinng
const getCar = async (name: string) => cache.get(name, async () => {
const { data } = await axios.get<Car>(`https://example.com/cars/${name}`);
return data;
});
@jmendiara
Copy link
Author

As a module: https://github.com/jmendiara/lru-pcache

published in npm

npm i lru-pcache

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment