Last active
December 14, 2020 14:15
-
-
Save jmendiara/82e9926d539bea807e5c7566f836ee17 to your computer and use it in GitHub Desktop.
LRU Cache for Promises in typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
As a module: https://github.com/jmendiara/lru-pcache
published in npm