| 1 | export function expiringCache<T, K=string>(ttlMs: number) { |
| 2 | if (!ttlMs) |
| 3 | throw Error('invalid TTL') |
| 4 | const o = new Map<K,T>() |
| 5 | return Object.assign(o, { |
| 6 | invalidate, |
| 7 | // creator can return undefined if the value should not be cached |
| 8 | try(k: K, creator: (k: K) => T): T { |
| 9 | let ret = o.get(k) |
| 10 | if (ret === undefined) { // undefined = missing, as we don't accept this value in our cache |
| 11 | ret = creator(k) |
| 12 | if (ret !== undefined) { |
| 13 | o.set(k, ret) |
| 14 | Promise.resolve(ret).then(v => { |
| 15 | if (v === undefined) // even in a promise, we'll consider undefined as a request to cancel the caching |
| 16 | invalidate(k) |
| 17 | }, () => {}) // avoid js warning |
| 18 | .finally(() => setTimeout(() => invalidate(k), ttlMs)) // wait for async (in case) before starting the timer |
| 19 | } |
| 20 | } |
| 21 | return ret |
| 22 | }, |
| 23 | }) |
| 24 | |
| 25 | function invalidate(k: K) { |
| 26 | o.delete(k) |
| 27 | } |
| 28 | } |
| 29 |