cache
This commit is contained in:
32
lib/feeds.ts
32
lib/feeds.ts
@@ -5,24 +5,36 @@ import { computeGroupTables, computeThirdPlaceTable } from "./standings";
|
|||||||
import { FIFA_GROUP_MAP, FIFA_STAGE_MAP } from "./fifa-constants";
|
import { FIFA_GROUP_MAP, FIFA_STAGE_MAP } from "./fifa-constants";
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Caching: einfacher In-Memory-Cache mit TTL.
|
// Caching: In-Memory-Cache mit TTL + stale-while-revalidate.
|
||||||
|
// Bei abgelaufenem Cache wird der alte Wert sofort zurückgegeben und die
|
||||||
|
// Erneuerung im Hintergrund angestoßen (keine Wartezeit für den Nutzer).
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
interface CacheEntry<T> { value: T; expires: number; }
|
interface CacheEntry<T> { value: T; expires: number; refreshing?: boolean; }
|
||||||
const cache = new Map<string, CacheEntry<unknown>>();
|
const cache = new Map<string, CacheEntry<unknown>>();
|
||||||
|
|
||||||
export async function cached<T>(key: string, ttlMs: number, fn: () => Promise<T>): Promise<T> {
|
export async function cached<T>(key: string, ttlMs: number, fn: () => Promise<T>): Promise<T> {
|
||||||
const hit = cache.get(key) as CacheEntry<T> | undefined;
|
const hit = cache.get(key) as CacheEntry<T> | undefined;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Frischer Cache → direkt zurück
|
||||||
if (hit && hit.expires > now) return hit.value;
|
if (hit && hit.expires > now) return hit.value;
|
||||||
try {
|
|
||||||
const value = await fn();
|
// Abgelaufen, aber vorhanden → sofort alten Wert liefern, im Hintergrund erneuern
|
||||||
cache.set(key, { value, expires: now + ttlMs });
|
if (hit) {
|
||||||
return value;
|
if (!hit.refreshing) {
|
||||||
} catch (err) {
|
hit.refreshing = true;
|
||||||
// Bei Upstream-Fehler abgelaufenen Cache weiterverwenden, statt hart zu failen.
|
fn()
|
||||||
if (hit) return hit.value;
|
.then((value) => { cache.set(key, { value, expires: Date.now() + ttlMs }); })
|
||||||
throw err;
|
.catch((err) => { console.error(`[cache] Hintergrund-Refresh fehlgeschlagen (${key}):`, err); })
|
||||||
|
.finally(() => { const e = cache.get(key) as CacheEntry<T> | undefined; if (e) e.refreshing = false; });
|
||||||
|
}
|
||||||
|
return hit.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kaltstart: kein Cache → synchron laden
|
||||||
|
const value = await fn();
|
||||||
|
cache.set(key, { value, expires: now + ttlMs });
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user