Express/Ejs/Node/Mongodb 多路由简化及症状、身体部位统计实现
问题根因说明
现有路由的核心问题是使用了回调式的MongoDB查询,
res.render属于同步执行代码,会在三个异步查询的回调触发前就执行,最终渲染的页面无法拿到真实的用户业务数据。
分步实现方案
1. 业务统计函数开发
针对SymptomModel数据集的统计需求,封装独立纯函数,方便复用、测试:
/** * 计算症状数据集的统计指标 * @param {Array} symptoms - 近30天的用户症状记录数组 * @returns {Object} 统计结果 */ function calculateSymptomStats(symptoms) { // 空数据边界处理,避免除零、空值报错 if (!symptoms.length) { return { avgPainLevel: 0, mostCommonBodyPart: '暂无记录' } } // 计算平均疼痛等级:求和后除以总记录数,保留1位小数 const totalPain = symptoms.reduce((sum, item) => sum + (item.painLevel || 0), 0) const avgPainLevel = (totalPain / symptoms.length).toFixed(1) // 统计最常记录的身体部位 const bodyPartCount = {} symptoms.forEach(item => { const part = item.bodyPart || '未填写' bodyPartCount[part] = (bodyPartCount[part] || 0) + 1 }) // 按出现次数降序排序,取频次最高的部位 const sortedParts = Object.entries(bodyPartCount).sort((a, b) => b[1] - a[1]) const mostCommonBodyPart = sortedParts[0][0] return { avgPainLevel, mostCommonBodyPart } }
2. 优化后的/dashboard路由实现
- 将路由回调改为
async函数,用await等待异步查询完成 - 三个模型的查询无依赖关系,用
Promise.all并行执行,减少整体等待时间 - 所有异步操作包裹在
try/catch中做错误兜底,避免服务崩溃 - 所有数据获取、统计完成后,再触发页面渲染
// 保留原有引入的moment、三个Model、鉴权中间件等依赖 app.get('/dashboard', isLoggedIn, async (req, res) => { try { // 计算近30天的时间阈值,直接传Date对象避免字符串格式、时区匹配问题 const thirtyDaysAgo = moment().subtract(30, 'days').toDate() const userQuery = { postedBy: req.user._id, created: { $gte: thirtyDaysAgo } } // 并行执行三个模型的查询,等待全部返回结果 const [symptoms, treatments, doctors] = await Promise.all([ SymptomModel.find(userQuery).lean(), // lean()直接返回普通JS对象,减少Mongoose实例的性能开销 TreatmentModel.find(userQuery).lean(), DoctorModel.find(userQuery).lean() ]) // 调用统计函数处理症状数据 const symptomStats = calculateSymptomStats(symptoms) // 所有数据准备完成后再渲染页面,将全部结果传给Ejs模板 res.render('dashboard.ejs', { user: req.user.firstName, rangeStart: thirtyDaysAgo, symptoms, treatments, doctors, avgPainLevel: symptomStats.avgPainLevel, mostCommonBodyPart: symptomStats.mostCommonBodyPart }) } catch (err) { // 统一错误处理,记录日志后返回错误页面 console.error('Dashboard route error:', err) res.status(500).render('error.ejs', { message: '页面数据加载失败' }) } })
补充优化点:如果症状数据量较大,不需要把全量数据拉到内存里计算统计值,可以直接用MongoDB的聚合管道在数据库端完成平均疼痛、高频部位的计算,减少网络传输和服务端计算开销。
相关学习知识点参考
- Node.js事件循环基础:理解回调式异步代码与同步代码的执行顺序差异,掌握异步操作的触发时机
- Mongoose查询机制:了解Mongoose的
find()方法不传回调时返回的是可await的Query对象,掌握lean()等性能优化用法 - 异步流程控制:梳理从回调地狱到Promise、再到async/await的演进逻辑,掌握
Promise.all处理并行无依赖异步任务、串行await处理有依赖任务的适用场景 - Express路由错误处理:掌握async路由下的错误捕获逻辑,避免未处理的Promise拒绝导致服务崩溃
- MongoDB聚合管道:掌握数据库端聚合计算的写法,学会在数据库层完成统计类需求,降低服务端运算压力
内容的提问来源于stack exchange,提问作者sinNombre
相关产品推荐
相关产品推荐

