如何在Express+EJS项目中从MySQL取数渲染Chart.js图表?
问题解决:Express后端异步数据传递到EJS渲染Chart.js
核心问题分析
你的代码核心问题是异步逻辑处理混乱:
getChartData标记了async但内部用回调式connection.query,未返回Promise,导致await getChartData()拿不到实际数据,最终得到undefined,传递给EJS的是空值。- 嵌套的
connection.query直接同步调用,根本无法获取异步查询结果,后续代码会跳过等待直接执行。
修复步骤
1. 封装MySQL查询为Promise
把回调式的connection.query转为Promise,让async/await能正确处理异步流程:
// 工具函数:将connection.query封装为Promise function query(sql, params) { return new Promise((resolve, reject) => { connection.query(sql, params, (error, results) => { if (error) reject(error); else resolve(results); }); }); }
2. 修正getChartData的异步逻辑
用await替代回调,确保所有数据库查询完成后再处理数据:
async function getChartData() { const now = new Date(); const formattedDate = now.toISOString().split('T')[0]; try { // 等待sample表查询结果 const sampleResults = await query("SELECT * FROM sample WHERE Date = ?", [formattedDate]); if (sampleResults.length === 0) { console.log("今日无数据,使用测试数据"); return [ { x: 50, y: 7 }, { x: 60, y: 8 }, { x: 70, y: 8 }, { x: 80, y: 9 }, { x: 90, y: 9 }, { x: 100, y: 9 }, { x: 110, y: 10 }, { x: 120, y: 11 }, { x: 130, y: 14 }, { x: 140, y: 14 }, { x: 150, y: 15 } ]; } const AveragesList = []; const sampleHoles = [2, 12, 16]; for (const holeid of sampleHoles) { // 依次等待每个嵌套查询完成 const holeRecords = await query("SELECT * FROM holes WHERE Id = ?", [sampleResults[0][holeid]]); const holeRecord = holeRecords[0]; const upSlopeRecords = await query("SELECT * FROM upslopespeed WHERE UpSlopeID = ?", [holeRecord.UpSlopeSpeedID]); const upSlopeData = upSlopeRecords[0]; const downSlopeRecords = await query("SELECT * FROM downslopespeed WHERE DownSlopeID = ?", [holeRecord.DownSlopeSpeedID]); const downSlopeData = downSlopeRecords[0]; // 计算平均速度 const backLeftSpeed = (upSlopeData.BackLeft * 2) / (upSlopeData.BackLeft + downSlopeData.BackLeft); const backCentreSpeed = (upSlopeData.BackCentre * 2) / (upSlopeData.BackCentre + downSlopeData.BackCentre); const backRightSpeed = (upSlopeData.BackRight * 2) / (upSlopeData.BackRight + downSlopeData.BackRight); const AverageSpeed = (backLeftSpeed + backCentreSpeed + backRightSpeed) / 3; AveragesList.push(AverageSpeed); } return [ {x:2, y:AveragesList[0]}, {x:12, y:AveragesList[1]}, {x:16, y:AveragesList[2]} ]; } catch (error) { console.error("数据库查询错误:", error); // 出错时返回兜底数据 return [ { x: 50, y: 7 }, { x: 60, y: 8 }, { x: 70, y: 8 } ]; } }
3. 修正路由函数的异步处理
在路由中用await正确获取数据,直接传递对象给EJS,避免提前JSON.stringify:
async function DashboardDetermine(req, res) { if (req.session.user.isAdmin === 1) { try { // 等待用户列表查询完成 const userResults = await query("SELECT * FROM user WHERE admin = 0"); // 等待图表数据查询完成 const scatterGraphData = await getChartData(); console.log(scatterGraphData); // 直接传递对象给EJS模板 res.render('dashboardAdmin', { user: userResults, scatterGraphData }); } catch (error) { console.error('加载数据失败:', error); req.flash('error_msg', '加载用户数据时发生错误。'); res.redirect('/dashboard'); } } else { res.render("dashboard"); } }
4. 修正EJS模板的数据注入
在EJS中把图表数据转为JSON字符串,存入canvas的dataset:
<body> <div class="container"> <div class="chart"> <!-- 用单引号包裹JSON字符串,避免双引号冲突 --> <canvas id="myChart" style="width:100%;max-width:700px" data-scatter-data='<%= JSON.stringify(scatterGraphData) %>'></canvas> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script> <script> document.addEventListener('DOMContentLoaded', function () { const scatterGraphData = JSON.parse(document.getElementById('myChart').dataset.scatterData); new Chart("myChart", { type: "scatter", data: { datasets: [{ pointRadius: 4, pointBackgroundColor: "rgb(0,0,255)", data: scatterGraphData }] }, options: { legend: { display: false }, scales: { xAxes: [{ ticks: { min: 40, max: 160 } }], yAxes: [{ ticks: { min: 6, max: 16 } }], } } }); }); </script> </body>
更高效的优化方案
- 批量查询:用JOIN语句合并多次嵌套查询,将
sample、holes、upslopespeed、downslopespeed关联,一次查询获取所有需要的数据,减少数据库请求次数。 - 缓存数据:如果果岭数据不会实时频繁更新,可使用Redis缓存查询结果,避免重复查询数据库。
内容的提问来源于stack exchange,提问作者Ben Northridge
相关产品推荐
相关产品推荐

