better code: improved expiringCache
Massimo Melina committed
Dec 24, 2025 at 14:20 UTC
f2a0946bb7a8ad67f7de5d8fea519d058798024a
2 files changed
+20
-9
src/expiringCache.ts
+17
-6
@@ -1,13 +1,24 @@
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, {
4
- try(k: K, creator: () => T): T {
6
+ // creator can return undefined if the value should not be cached. `invalidate` is useful in case you have some custom logic for it, other than ttl.
7
+ try(k: K, creator: (invalidate: () => void) => T): T {
8
let ret = o.get(k)
6
- if (ret === undefined) {
7
- ret = creator()
8
- o.set(k, ret)
9
- // in case of async, wait for it to be done before starting the timer
10
- Promise.resolve(ret).finally(() => setTimeout(() => o.delete(k), ttlMs)).catch(() => {})
9
+ if (ret === undefined) { // undefined = missing, as we don't accept this value in our cache
10
+ ret = creator(invalidate)
11
+ if (ret !== undefined) {
12
+ o.set(k, ret)
13
+ Promise.resolve(ret).then(v => {
14
+ if (v === undefined) // even in a promise, we'll consider undefined as a request to cancel the caching
15
+ invalidate()
16
+ }, () => {}) // avoid js warning
17
+ .finally(() => setTimeout(invalidate, ttlMs)) // wait for async (in case) before starting the timer
18
+ }
19
+ function invalidate() {
20
+ o.delete(k)
21
+ }
22
}
23
return ret
24
},
src/lang.ts
+3
-3
@@ -1,5 +1,5 @@
1
import Koa from 'koa'
2
-import { CFG, hasProp, onlyTruthy, tryJson } from './misc'
2
+import { CFG, Dict, hasProp, onlyTruthy, tryJson } from './misc'
3
import { expiringCache } from './expiringCache'
4
import { readFile } from 'fs/promises'
5
import { defineConfig } from './config'
@@ -21,7 +21,7 @@ export function file2code(fn: string) {
21
return fn.replace(PREFIX, '').replace(SUFFIX, '')
22
}
23
24
-const cache = expiringCache(3_000) // 3 seconds for both a good dx and acceptable performance
24
+const cache = expiringCache<Dict>(3_000) // 3 seconds for both a good dx and acceptable performance
25
export async function getLangData(ctxOrLangCsv: Koa.Context | string) {
26
if (typeof ctxOrLangCsv !== 'string') {
27
const ctx = ctxOrLangCsv
@@ -33,7 +33,7 @@ export async function getLangData(ctxOrLangCsv: Koa.Context | string) {
33
const csv = ctxOrLangCsv.toLowerCase()
34
return cache.try(csv, async () => {
35
const langs = csv.split(',')
36
- const ret: any = {}
36
+ const ret: Dict = {}
37
let i = 0
38
while (i < langs.length) {
39
let k = langs[i] || '' // shut up ts