You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Express渲染EJS页面读取singleHighscore属性undefined报错排查

运行时错误:Cannot read properties of undefined (reading 'username')

问题表现

  • 修复网站首页功能时,页面持续抛出运行时错误:Cannot read properties of undefined (reading 'username')
  • 删除报错位置的username属性读取代码后,错误变更为读取其他属性时触发同类报错,可排除单个属性缺失的问题,确认根因是传入模板的singleHighscore对象本身为undefined

关联代码

  1. 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;
  1. EJS模板渲染代码
<div>
  <p><%=singleHighscore.username%></p>
  <p><%=singleHighscore.game.name%></p>
  <p><%=singleHighscore.score%></p>
  <p><%=singleHighscore.active%></p>
</div>
  1. 数据层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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:45:18