MongoDB与Express错误处理:res未定义间歇性问题排查
问题描述
我用Node/Express搭建了一个与MongoDB交互的服务器,核心数据库操作函数withDB代码如下:
const withDB = async (operations, res) => { try { const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true }); const db = client.db('database-name'); await operations(db); client.close(); } catch (error) { res.status(500).json({ message: 'Error connecting to db', error }); } }
调用示例(以/api/employees接口为例):
app.get('/api/employees', async (req, res) => { withDB(async (db) => { const tabInfo = await db.collection('users').find().toArray() res.status(200).json(tabInfo); }, res); });
其他CRUD接口运行正常,但/api/employees接口会间歇性触发以下错误导致服务器崩溃:
res.status(500).json({ ^ TypeError: Cannot read properties of undefined (reading 'status')
该请求仅在React应用的某个页面调用,无法稳定复现崩溃场景。需要解决两个问题:一是正确处理错误避免服务器崩溃,二是实现服务器崩溃自动重启机制。
解决方案
一、修复错误避免服务器崩溃
1. 给res参数添加空值校验
错误根源是catch块中res为undefined,可能是客户端主动断开请求(比如用户快速切换页面)导致res对象被释放。在withDB的catch块中先校验res的可用性:
const withDB = async (operations, res) => { try { const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true }); const db = client.db('database-name'); await operations(db); client.close(); } catch (error) { // 先判断res是否可用,避免调用undefined的方法 if (res && typeof res.status === 'function') { res.status(500).json({ message: 'Error connecting to db', error: error.message }); } else { // 无可用res时仅打印日志,防止崩溃 console.error('Database error with no valid response object:', error); } } }
2. 优化withDB调用逻辑,处理异步错误
当前withDB是异步函数,但调用时未加await,若operations内部抛错时res已失效,会引发异常。修改接口调用逻辑:
app.get('/api/employees', async (req, res) => { try { await withDB(async (db) => { const tabInfo = await db.collection('users').find().toArray() // 响应前检查是否已发送过响应,避免重复响应报错 if (!res.headersSent) { res.status(200).json(tabInfo); } }, res); } catch (error) { console.error('/api/employees接口错误:', error); if (!res.headersSent) { res.status(500).json({ message: '服务器内部错误' }); } } });
3. 改用MongoDB连接池(推荐)
每次请求创建新连接不仅效率低,还可能因连接未正确关闭引发资源泄漏。改用连接池复用连接:
// 全局初始化连接池,仅执行一次 let dbInstance; const initMongoDB = async () => { const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true, poolSize: 10 // 设置连接池大小,按需调整 }); dbInstance = client.db('database-name'); console.log('MongoDB连接池初始化完成'); }; // 初始化连接池 initMongoDB().catch(err => console.error('MongoDB初始化失败:', err)); // 修改withDB函数,无需每次创建新连接 const withDB = async (operations) => { if (!dbInstance) { throw new Error('MongoDB连接未初始化'); } try { await operations(dbInstance); } catch (error) { throw error; // 将错误抛给上层接口处理 } } // 接口调用示例 app.get('/api/employees', async (req, res) => { try { await withDB(async (db) => { const tabInfo = await db.collection('users').find().toArray() if (!res.headersSent) { res.status(200).json(tabInfo); } }); } catch (error) { console.error('/api/employees接口错误:', error); if (!res.headersSent) { res.status(500).json({ message: '服务器内部错误', error: error.message }); } } });
二、实现服务器崩溃自动重启
1. 使用PM2进程管理器
这是Node.js生产环境最常用的进程管理工具,能自动重启崩溃的服务,还支持日志监控、负载均衡等功能:
- 安装:
npm install pm2 -g - 启动服务:
pm2 start app.js(假设你的入口文件是app.js) - 常用命令:
- 查看服务状态:
pm2 status - 查看实时日志:
pm2 logs - 手动重启服务:
pm2 restart app
- 查看服务状态:
2. 全局错误捕获兜底
在Express中添加全局错误处理中间件,捕获所有未处理的异常:
// 全局错误处理中间件,需放在所有路由定义之后 app.use((err, req, res, next) => { console.error('未捕获的接口错误:', err); if (!res.headersSent) { res.status(500).json({ message: '服务器内部错误' }); } }); // 捕获未处理的Promise拒绝 process.on('unhandledRejection', (reason, promise) => { console.error('未处理的Promise拒绝:', promise, '原因:', reason); }); // 捕获未捕获的同步异常 process.on('uncaughtException', (error) => { console.error('未捕获的同步异常:', error); // 优雅退出,PM2会自动重启服务 process.exit(1); });
内容的提问来源于stack exchange,提问作者Nathan Field
相关产品推荐
相关产品推荐

