Nest.js缓存报错:返回null触发“非可缓存值”异常,求排查
问题描述
我编写了如下的cache-config.service.ts文件,按照node-cache-manager文档实现了isCacheableValue函数:
... @Injectable() export class CacheConfigService implements CacheOptionsFactory { host: string; ... constructor(private configService: ConfigService<RedisConfig, true>) { this.host = this.configService.getOrThrow<string>('redis.host', { infer: true, }); ... } createCacheOptions(): CacheModuleOptions<ClientOpts> { return { store: redisStore, host: this.host, isCacheableValue(value) { return value !== null && value !== false && value !== undefined; }, }; } }
但当请求不存在的用户(如/users/999)返回null时,服务器崩溃,抛出错误:Error: "null" is not a cacheable value。请问哪里配置错误?
解决方法
问题核心是:你的isCacheableValue逻辑本身没问题,但NestJS缓存拦截器或底层redis-store在处理null值时,未正确跳过缓存操作,反而尝试存储null触发了错误。以下是几种可行的解决方式:
方法一:调整缓存配置,允许并转换null值
修改isCacheableValue允许缓存null,同时通过自定义序列化将null转为可存储的格式,读取时再恢复:
createCacheOptions(): CacheModuleOptions<ClientOpts> { return { store: redisStore, host: this.host, isCacheableValue(value) { // 允许缓存null、非false和非undefined的值 return value !== false && value !== undefined; }, // 自定义序列化逻辑 serialize: (value) => { if (value === null) { return 'NULL_PLACEHOLDER'; } return JSON.stringify(value); }, deserialize: (value) => { if (value === 'NULL_PLACEHOLDER') { return null; } return JSON.parse(value); }, }; }
方法二:自定义缓存拦截器,手动跳过非缓存值
如果不想缓存null,可以自定义拦截器,在存储前检查值是否符合缓存条件,不符合则直接跳过:
import { CacheInterceptor, ExecutionContext, Inject, CACHE_MANAGER } from '@nestjs/common'; import { Cache } from 'cache-manager'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; export class CustomCacheInterceptor extends CacheInterceptor { constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) { super(cacheManager); } intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const cacheKey = this.trackBy(context); if (!cacheKey) { return next.handle(); } return next.handle().pipe( tap(async (responseValue) => { // 调用配置的isCacheableValue判断是否缓存 const isCacheable = (this.cacheManager.store as any).isCacheableValue?.(responseValue); if (isCacheable) { await this.cacheManager.set(cacheKey, responseValue); } }), ); } }
之后在控制器或模块上替换默认拦截器:
@UseInterceptors(CustomCacheInterceptor) export class UsersController { // 控制器逻辑 }
方法三:业务层直接标记跳过缓存
在查询结果为null时,通过自定义标记告知拦截器跳过缓存(需配合自定义拦截器使用):
@Get(':id') async getUser(@Param('id') id: string, @Context() context: ExecutionContext) { const user = await this.userService.findById(id); if (!user) { // 标记跳过缓存 context.switchToHttp().getRequest().skipCache = true; return null; } return user; }
然后在自定义拦截器中检查这个标记:
// 在CustomCacheInterceptor的intercept方法中添加 const request = context.switchToHttp().getRequest(); if (request.skipCache) { return next.handle(); }
内容的提问来源于stack exchange,提问作者Swix
相关产品推荐
相关产品推荐

