Skip to content

Instantly share code, notes, and snippets.

@slonoed
Last active Feb 15, 2018
Embed
What would you like to do?
Simple memory cache
/*
* Simple implementation of memory cache
*
* Is use async API in case we replace it with redis.
*
* Keys should be symbols.
*/
const DEFAULT_TTL = 5 * 60 * 1000
const CLEAN_INTERVAL = DEFAULT_TTL * 1.5
export default class Cache {
constructor(options = {}) {
this.ttl = options.ttl || DEFAULT_TTL
this.storage = new Map()
this.activeGetters = new Map()
if (process.env.NODE_ENV === 'production') {
setInterval(() => {
for (const [key, item] of this.storage) {
if (item && item.expiredAt < Date.now()) {
this.storage.delete(key)
}
}
}, CLEAN_INTERVAL)
}
}
async set(key, value, ttl) {
if (typeof key !== 'symbol') {
throw new Error('Key should be Symbol')
}
const item = {
expiredAt: Date.now() + (ttl || this.ttl),
value,
}
this.storage.set(key, item)
}
async get(key) {
if (typeof key !== 'symbol') {
throw new Error('Key should be Symbol')
}
const item = this.storage.get(key)
if (!item || item.expiredAt < Date.now()) {
return null
}
return item.value
}
// Return value from cache. If no value in cache, call getter
// write result to cache and return. If previous getter still running
// wait for it and return result.
async getOrUpdate(key, getter, ttl) {
if (typeof key !== 'symbol') {
throw new Error('Key should be Symbol')
}
const item = this.storage.get(key)
if (item && item.expiredAt >= Date.now()) {
return item.value
}
const activeGetter = this.activeGetters.get(key)
if (activeGetter) {
return activeGetter
}
const currentGetter = getter()
this.activeGetters.set(key, currentGetter)
let value
try {
value = await currentGetter
await this.set(key, value, ttl)
} finally {
this.activeGetters.delete(key)
}
return value
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment