Node.js TypeScript项目中使用node-redis客户端出现「Error: The client is closed」错误的求助
解决Redis客户端报错
Error: The client is closed的问题 这个问题我之前处理过,核心原因主要有两个:Redis客户端初始化完成前就被调用,以及legacy模式下的API使用方式不正确。下面给你详细的解决方案:
一、先确保Redis连接完成后再启动服务器
你的index.ts里是并行执行了Redis初始化和服务器启动,这会导致服务器已经启动,但Redis还没建立好连接,控制器就去调用客户端,自然会报"client is closed"。
修改index.ts,等待Redis初始化完成后再启动服务器:
import { init } from 'dataSource/redis'; (async () => { try { // 先确保Redis连接成功 await init(); // 再启动服务器 app.listen(PORT,() => { Logger.info(`Server Started in port : ${PORT}!`); }); } catch (err) { Logger.error('Failed to initialize services', err); // 初始化失败直接退出进程 process.exit(1); } })();
二、修正Redis客户端的API使用方式
你开启了legacyMode: true,这意味着你使用的是Redis客户端的旧版回调式API,但你直接同步调用setEx,既没有处理回调也没有promisify,再加上连接未就绪,就会触发错误。这里提供两种解决方案:
方案1:继续使用legacy模式,对方法进行promisify
在你的Redis连接文件dataSource/redis.ts中,对需要使用的Redis方法进行promisify处理:
import { createClient } from 'redis'; import { promisify } from 'util'; import Logger from 'utils/logger'; const { REDIS_URL = 'redis://localhost:6379' } = process.env; const options = { legacyMode: true, url: REDIS_URL, }; const client = createClient(options); // 对需要的方法进行promisify并绑定客户端 const setExAsync = promisify(client.setEx).bind(client); const getAsync = promisify(client.get).bind(client); // 其他需要的方法如del、hget等也可以按此方式处理 // 事件监听保持不变 client.on('connect', () => { Logger.info("Connected to Redis"); }); client.on('error', err => { Logger.error('redis error: ', err); // 注意:legacy模式下不要在error事件里递归调用init,会导致无限重连循环 }); client.on('ready', () => { Logger.info("redis is ready"); }); client.on('end', () => { Logger.info("redis connection is ended"); }); client.on('reconnecting', () => { Logger.info("redis connection is reconnecting"); }); const init = async () => { await client.connect(); }; // 导出promisified的方法而非原始client export { init, setExAsync, getAsync };
然后在控制器中异步调用这些promisified的方法:
import { setExAsync } from 'datasource/redis'; // 必须在异步函数中调用 async function yourControllerMethod() { try { const expireTime = Number(process.env.REDIS_EXPIRE_TIME); if (isNaN(expireTime)) { throw new Error('REDIS_EXPIRE_TIME is not a valid number'); } await setExAsync("Key", expireTime, "VALUE"); Logger.info('Redis key set successfully'); } catch (err) { Logger.error('Failed to set Redis key:', err); } }
方案2:改用Redis客户端的新版Promise API(推荐)
如果不需要兼容旧代码,建议直接移除legacyMode,使用新版的Promise式API,更符合TypeScript的异步编程习惯:
修改dataSource/redis.ts:
import { createClient } from 'redis'; import Logger from 'utils/logger'; const { REDIS_URL = 'redis://localhost:6379' } = process.env; // 移除legacyMode配置 const options = { url: REDIS_URL, }; const client = createClient(options); // 事件监听保持不变 client.on('connect', () => { Logger.info("Connected to Redis"); }); client.on('error', err => { Logger.error('redis error: ', err); // 新版客户端默认自带自动重连机制,无需手动调用init }); client.on('ready', () => { Logger.info("redis is ready"); }); client.on('end', () => { Logger.info("redis connection is ended"); }); client.on('reconnecting', () => { Logger.info("redis connection is reconnecting"); }); const init = async () => { try { await client.connect(); Logger.info("Redis client initialized successfully"); } catch (err) { Logger.error('Failed to connect to Redis:', err); throw err; // 抛出错误让上层处理 } }; // 直接导出新版客户端,所有方法都是Promise-based export { init, client };
然后在控制器中异步调用客户端方法:
import { client as redisClient } from 'datasource/redis'; async function yourControllerMethod() { try { const expireTime = Number(process.env.REDIS_EXPIRE_TIME); if (isNaN(expireTime)) { throw new Error('REDIS_EXPIRE_TIME must be a valid number'); } await redisClient.setEx("Key", expireTime, "VALUE"); Logger.info('Redis key set successfully'); } catch (err) { Logger.error('Redis operation failed:', err); } }
三、额外注意事项
- 始终确保Redis操作在异步函数中执行,并用
try/catch捕获错误,避免未处理的Promise rejection - 检查
process.env.REDIS_EXPIRE_TIME是否存在且为有效数字,避免传入NaN导致Redis操作失败 - 新版Redis客户端默认带有自动重连机制,无需在
error事件中手动调用init,否则可能导致无限循环
内容的提问来源于stack exchange,提问作者Uday Mishra
相关产品推荐
相关产品推荐

