开发环境
-
Nest.js v10.0.1
其他 npm 包版本如下
"@nestjs/cache-manager": "^2.0.1",
"cache-manager": "^5.2.3",
"cache-manager-redis-store": "^3.0.1",
"cache-manager-redis-yet": "^4.1.2",
"redis": "^4.6.7",
背景
最近笔者在学习 node.js,选择了Nestjs作为后端框架,在掘金上找了篇文章(后文简称教程)跟着一起敲,那位作者使用的是Nestjs v8的版本,因此在redis相关的章节遇到了问题,最后参考了官方文档官方文档以及相关npm包的Github Issue中的内容,暂时解决了。笔者百度了很久Nestjs相关的redis教程,普遍都比较老了,也基本是大同小异,因此笔者打算写这篇文章作为记录。
问题复现
教程中的代码:
import { ConfigModule, ConfigService } from '@nestjs/config';
import { RedisCacheService } from './redis-cache.service';
import { CacheModule, Module, Global } from '@nestjs/common';
import * as redisStore from 'cache-manager-redis-store';
@Module({
imports: [
CacheModule.registerAsync({
isGlobal: true,
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
return {
store: redisStore,
host: configService.get('REDIS_HOST'),
port: configService.get('REDIS_PORT'),
db: 0, //目标库,
auth_pass: configService.get('REDIS_PASSPORT') // 密码,没有可以不写
};
},
}),
],
providers: [RedisCacheService],
exports: [RedisCacheService],
})
export class RedisCacheModule {}
发现useFactory函数部分报错,是一个TS错误:
解决问题?
问题出现的原因是此处useFactory函数需要返回一个类型为Promise<CacheModuleOptions<StoreConfig>> | CacheModuleOptions<StoreConfig>的值,很显然笔者此时返回的对象有问题。
export interface CacheModuleAsyncOptions<StoreConfig extends Record<any, any> = Record<string, any>> extends ConfigurableModuleAsyncOptions<CacheModuleOptions<StoreConfig>, keyof CacheOptionsFactory> {
useExisting?: Type<CacheOptionsFactory<StoreConfig>>;
useClass?: Type<CacheOptionsFactory<StoreConfig>>;
useFactory?: (...args: any[]) => Promise<CacheModuleOptions<StoreConfig>> | CacheModuleOptions<StoreConfig>;
inject?: any[];
extraProviders?: Provider[];
isGlobal?: boolean;
}
从报错中可以看出,问题基本出在这个store上,而这个store的类型是由cache-manager-redis-store 这个npm包决定的,因此查看这个包有什么变动。
由于版本的升级,该包已经可以按需引入了,因此我们使用
import { redisStore } from 'cache-manager-redis-store';
同时,写法也有所变化:
@Module({
imports: [
CacheModule.registerAsync({
...
useFactory: async (configService: ConfigService) => {
return {
store: redisStore({
socket: {
host: configService.get('REDIS_HOST'),
port: configService.get('REDIS_PORT'),
},
database: configService.get('REDIS_DB'),
password: configService.get('REDIS_PASSWORD'),
}),
};
},
...
})
发现还是报错,但是错误信息不同:
核心是这句:Type 'Promise<RedisStore>' is not assignable to type 'string | CacheStoreFactory | CacheStore'.
也就是说类型还是不匹配。
查阅官方文档,发现文档相当之老,但是有一个Warning引起了笔者的注意:
当场点开了这个issue,让我康康。
解决问题!
这个npm包的作者提出了解决方案://@ts-ignore 😅
加入代码后,能正常的跑起来了,但是在写入值的时候,又报错了:
[Nest] 75156 - 06/28/2023, 3:10:41 PM ERROR [ExceptionsHandler] store.set is not a function
于是继续看这个issue,发现了解决方案,似乎是和异步有关系
@Module({
imports: [
CacheModule.registerAsync({
...
//@ts-ignore
useFactory: async (configService: ConfigService) => {
const store = await redisStore({
...
});
return {
store,
};
},
}),
...
})
这样报错就解决了,redis也能正常使用了
但是笔者作为一个高贵的TS使用者,怎么能接受自己的代码中有@ts-ignore出现呢???这也太不专业了,正巧,issue中也有老哥跟笔者一个想法,因此「 优化版 」出现了。
@Module({
imports: [
CacheModule.registerAsync({
...
useFactory: async (configService: ConfigService) => {
const store = await redisStore({
...
});
return {
store: store as unknown as CacheStore,
};
},
}),
...
})
看起来舒服了一点,但是issue中有硬核老哥觉得这还不行,作者一直不改,那就自己改,于是魔改了这个包,发布了另一个包cache-manager-redis-yet,这个替换这个包就可以了,两者就最后一个单词不同。
cache-manager-redis-store
cache-manager-redis-yet
至此问题基本解决了:
import { redisStore } from 'cache-manager-redis-yet';
import type { RedisClientOptions } from 'redis';
...
@Module({
imports: [
CacheModule.registerAsync<RedisClientOptions>({
...
useFactory: async (configService: ConfigService) => {
const store = await redisStore({
...
});
return {
store: store,
};
},
}),
...
})
总结
解决方式有两种(前提是你已经使用了新的写法):
- 使用
@ts-ignore直接忽略报错 - 使用
cache-manager-redis-yet替代原来的cache-manager-redis-store
希望笔者的文章能帮到你。