UNPKG

transitory

Version:

In-memory cache with high hit rates via LFU eviction. Supports time-based expiration, automatic loading and metrics.

95 lines (76 loc) 2.46 kB
import { Cache } from '../Cache'; import { CommonCacheOptions } from '../CommonCacheOptions'; import { KeyType } from '../KeyType'; import { WrappedCache } from '../WrappedCache'; import { Loader } from './Loader'; import { LoadingCache } from './LoadingCache'; const DATA = Symbol('loadingData'); /** * Options available for a loading cache. */ export interface LoadingCacheOptions<K extends KeyType, V> extends CommonCacheOptions<K, V> { loader?: Loader<K, V> | undefined | null; parent: Cache<K, V>; } interface LoadingCacheData<K extends KeyType, V> { promises: Map<K, Promise<V>>; loader: Loader<K, V> | null; } /** * Extension to another cache that will load items if they are not cached. */ export class DefaultLoadingCache<K extends KeyType, V> extends WrappedCache<K, V> implements LoadingCache<K, V> { private [DATA]: LoadingCacheData<K, V>; public constructor(options: LoadingCacheOptions<K, V>) { super(options.parent, options.removalListener || null); this[DATA] = { promises: new Map(), loader: options.loader || null }; } /** * Get cached value or load it if not currently cached. Updates the usage * of the key. * * @param key - * key to get * @param loader - * optional loader to use for loading the object * @returns * promise that resolves to the loaded value */ public get(key: K, loader?: Loader<K, V>): Promise<V> { const currentValue = this.getIfPresent(key); if(currentValue !== null) { return Promise.resolve(currentValue); } const data = this[DATA]; // First check if we are already loading this value let promise = data.promises.get(key); if(promise) return promise; // Create the initial promise if we are not already loading if(typeof loader !== 'undefined') { if(typeof loader !== 'function') { throw new Error('If loader is used it must be a function that returns a value or a Promise'); } promise = Promise.resolve(loader(key)); } else if(data.loader) { promise = Promise.resolve(data.loader(key)); } if(! promise) { throw new Error('No way to load data for key: ' + key); } // Enhance with handler that will remove promise and set value if success const resolve = () => data.promises.delete(key); promise = promise.then(result => { this.set(key, result); resolve(); return result; }).catch(err => { resolve(); throw err; }); data.promises.set(key, promise); return promise; } }