Express渲染EJS页面读取singleHighscore属性undefined报错排查
运行时错误:Cannot read properties of undefined (reading 'username')
问题表现
- 修复网站首页功能时,页面持续抛出运行时错误:
Cannot read properties of undefined (reading 'username') - 删除报错位置的
username属性读取代码后,错误变更为读取其他属性时触发同类报错,可排除单个属性缺失的问题,确认根因是传入模板的singleHighscore对象本身为undefined
关联代码
- Express路由(数据查询+模板传参)
const express = require("express"); const router = express.Router(); const db = require("../queries"); /* GET home page. */ router.get("/", async function (req, res, next) { const highscore = await db.getHighscore(1); const game = await db.getGame(1); const games = await db.getGames(10); const highscores = await db.getHighscores(10); const searchGames = await db.getSearch("C"); res.render("index", { title: "Express", games: games, highscores: highscores, singleHighscore: highscore, singleGame: game, searchGames: searchGames, }); }); module.exports = router;
- EJS模板渲染代码
<div> <p><%=singleHighscore.username%></p> <p><%=singleHighscore.game.name%></p> <p><%=singleHighscore.score%></p> <p><%=singleHighscore.active%></p> </div>
- 数据层
getHighscore查询方法
const getHighscore = async (id) => { let highscore; const pool = new Pool(credentials); await pool .query(`SELECT * FROM public.highscores WHERE id= ${id}`) .then((res) => { highscore = res.rows[0]; }); if (highscore && highscore.hasOwnProperty("gameid")) await pool .query(`SELECT * FROM public.games WHERE id= ${highscore.gameid}`) .then((res) => (highscore.game = res.rows[0])); return highscore; };
根因定位
直接触发报错的原因:db.getHighscore(1)执行后返回了undefined,模板读取undefined的属性必然连续抛出同类错误。返回undefined的场景有两种:
highscores表中不存在id=1的记录,SQL查询返回的res.rows为空数组,res.rows[0]取值为undefined,函数无其他兜底逻辑直接返回该值- 数据库查询过程抛出异常(比如连接失败、SQL语法错误),async函数没有捕获错误,会导致返回值异常
代码中存在的其他隐患:
- 每次调用
getHighscore都新建一个数据库连接池,会造成连接泄漏,服务运行一段时间后会耗尽数据库连接 - SQL语句直接拼接入参,存在SQL注入风险
- 关联查询game信息时没有判断查询结果是否存在,即使highscore查询成功,若对应game记录不存在,读取
singleHighscore.game.name时仍会抛出同类错误 - 路由层async函数没有异常捕获,查询报错时无法走Express默认错误处理逻辑,会直接挂掉请求
修复方案
1. 重构数据层查询逻辑
将连接池初始化移到模块全局避免重复创建,改用参数化查询规避注入风险,增加空值兜底返回默认结构,关联查询增加结果判断:
// 连接池全局初始化,整个模块复用同一个实例 const pool = new Pool(credentials); const getHighscore = async (id) => { // 初始化默认结构,从根源避免返回undefined let highscore = { username: "暂无用户", score: 0, active: false, game: { name: "暂无关联游戏" } }; // 参数化查询,避免SQL注入 const highscoreResult = await pool.query( "SELECT * FROM public.highscores WHERE id = $1", [id] ); if (highscoreResult.rows.length > 0) { highscore = { ...highscoreResult.rows[0] }; // 关联查询游戏信息,同样做空结果判断 if (highscore.gameid) { const gameResult = await pool.query( "SELECT * FROM public.games WHERE id = $1", [highscore.gameid] ); highscore.game = gameResult.rows.length > 0 ? gameResult.rows[0] : { name: "暂无关联游戏" }; } } return highscore; };
2. 模板层增加可选链做双重防护
即使数据层做了兜底,模板层使用可选链操作符可以进一步降低异常概率,同时可以自定义空状态展示文案:
<div> <p><%= singleHighscore?.username || '暂无用户' %></p> <p><%= singleHighscore?.game?.name || '暂无关联游戏' %></p> <p><%= singleHighscore?.score ?? 0 %></p> <p><%= singleHighscore?.active ? '已激活' : '未激活' %></p> </div>
3. 路由层补充异常捕获
给async路由块加try/catch,让查询异常可以走到Express的错误处理中间件,避免请求直接挂死:
router.get("/", async function (req, res, next) { try { const highscore = await db.getHighscore(1); const game = await db.getGame(1); const games = await db.getGames(10); const highscores = await db.getHighscores(10); const searchGames = await db.getSearch("C"); res.render("index", { title: "Express", games: games, highscores: highscores, singleHighscore: highscore, singleGame: game, searchGames: searchGames, }); } catch (err) { next(err); } });
内容的提问来源于stack exchange,提问作者Vanfyr
相关产品推荐
相关产品推荐

