TypeScript中如何为存在类型的数据加载逻辑添加记忆化缓存?
解决方案
现有ID方案的类型修复
你给DataLoader加ID的思路本身是合理的,只需要调整缓存类型和增加安全的类型断言即可,完全不需要使用any:
async function runValidation() { // 缓存值用unknown类型,比any安全,不允许随意操作 const memo = new Map<string, unknown>(); for (const validator of validators) { // 回调内的泛型参数A对应当前校验器的加载器返回值类型 await validator(async <A>([dataLoader, check]: Validator<A>) => { let cached = memo.get(dataLoader.id); if (!cached) { cached = await dataLoader.load(); memo.set(dataLoader.id, cached); } // 断言安全:只要你保证相同ID的DataLoader返回值类型完全一致,就不会有类型错误 return check(cached as A); }); } }
优化点:缓存Promise避免重复并发请求
你当前的实现存在一个小漏洞:如果同一个ID的加载器第一次请求还未返回,第二个同ID的请求进来时,还是会触发第二次重复加载。可以将缓存改为存储Promise,解决这个问题:
async function runValidation() { const memo = new Map<string, Promise<unknown>>(); for (const validator of validators) { await validator(async <A>([dataLoader, check]: Validator<A>) => { let cachedPromise = memo.get(dataLoader.id); if (!cachedPromise) { cachedPromise = dataLoader.load(); memo.set(dataLoader.id, cachedPromise); } const cached = await cachedPromise; return check(cached as A); }); } }
替代方案:无需ID的WeakMap实现
如果你的相同加载逻辑都会复用同一个DataLoader实例,不需要跨实例共享缓存,可以去掉ID字段,直接用WeakMap以加载器实例为键存储缓存,无需人工维护ID,还能自动触发垃圾回收,避免内存泄漏:
// 调整类型,去掉id字段 type DataLoader<A> = { load: () => Promise<A> } type Validator<A> = [dataLoader: DataLoader<A>, check: (input: A) => void]; type ExistentialValidator = <R>(run: <A>(validator: Validator<A>) => R) => R; declare const validators: ExistentialValidator[]; // 运行逻辑 async function runValidation() { const memo = new WeakMap<DataLoader<any>, Promise<unknown>>(); for (const validator of validators) { await validator(async <A>([dataLoader, check]: Validator<A>) => { let cachedPromise = memo.get(dataLoader); if (!cachedPromise) { cachedPromise = dataLoader.load(); memo.set(dataLoader, cachedPromise); } const cached = await cachedPromise; return check(cached as A); }); } }
两种方案选型建议
- 选ID方案:如果你需要跨不同DataLoader实例共享缓存(比如两个不同实例加载完全相同的资源),选择带ID的方案,仅需要做好ID的唯一性管控即可。
- 选WeakMap方案:如果你相同的加载逻辑都会复用同一个DataLoader实例,选择WeakMap方案,不需要维护ID,也不会有ID冲突的风险,内存占用更优。
内容的提问来源于stack exchange,提问作者ksaveljev
相关产品推荐
相关产品推荐

