You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 17:30:31