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

Node.js+PostgreSQL NBA统计应用错误处理方案问询

NBA统计应用错误处理问题

我正在开发一款NBA统计应用,已经把大量路由和数据库查询拆分到多个文件中,但对错误处理方式存在困惑。

相关代码

1. mutualScript.js中调用路由的函数

const getSeasonStatAvgLocal = async(stat, year, playerId, H_or_V) => {
    let url;

    if (H_or_V === 'home') {
        url = '/boxScoresTraditional/home/' + playerId + '/' + year; 
    } else {
        url = '/boxScoresTraditional/visitor/' + playerId + '/' + year; 
    } 
    let gameDetailsArray = await getJsonResponse(url);
    let statTotal = await getSeasonTotalOfStat(stat.toLowerCase(), gameDetailsArray);

    //let gamesPlayed = await getGamesPlayedInSeason(gameDetailsArray);
    let gamesPlayed = gameDetailsArray.length;
    let statAverage = statTotal / gamesPlayed;

    return Number.parseFloat(statAverage).toFixed(2);
}

2. mutualScript.js中的getJsonResponse函数

const getJsonResponse = async (url) => {
    console.log(url);
    const response = await fetch(url);
    try{
        if (response.ok){
            const jsonResponse = await response.json();
            return jsonResponse;
        }
    } catch(err){
        console.log(err);
    }
}

3. boxScoresTraditionalRoutes.js路由文件

const express = require('express');
const router = express.Router();
const boxScoreTraditional = require('../services/boxscorestraditionalQueries')

/**
 * @swagger 
 * /boxScoresTraditional/home/{playerid}/{season}:
 *   get:
 *     parameters:
 *       - in: path
 *         name: playerid
 *         schema:
 *           type: string
 *         required: true
 *         description: String ID of the player who's box scores we are getting
 *       - in: path
 *         name: season
 *         schema:
 *           type: string
 *         required: true
 *         description: String season of the box scores we are getting
 *     responses:
 *       '200':
 *         description: A successful response
 *       
 */
router.get('/home/:playerid/:season', boxScoreTraditional.getBoxScoreTraditionalHome);

router.get('/visitor/:playerid/:season', boxScoreTraditional.getBoxScoreTraditionalVisitor);

4. boxScoresTraditionalQueries.js数据库查询函数

const getBoxScoreTraditionalHome = (request, response) => {
    const {playerid, season} = request.params;
 
    let newSeason = JSON.stringify(season);
    let stringSeason = newSeason.replace(/"/g, '');
    db.query(`SELECT * FROM "boxscorestraditional${stringSeason}" 
              INNER JOIN "boxscoresummary${stringSeason}" 
              ON "boxscorestraditional${stringSeason}".game_id = "boxscoresummary${stringSeason}".game_id
              WHERE player_id = $1
              AND "boxscoresummary${stringSeason}".home_team_id = "boxscorestraditional${stringSeason}".team_id
              ORDER BY "boxscorestraditional${stringSeason}".id`, [playerid], (error, results) => {
      if (error) {
         throw error
      }
      response.status(200).json(results.rows)
    })
}

当前问题与需求

调用getBoxScoreTraditionalHome出现错误时,应用会崩溃,错误无法返回至前端getJsonResponse的catch块。我希望能在前端统一处理所有错误,或通过后端错误中间件处理。现咨询:如何在上述数据库查询的错误块中调用Express错误处理中间件?


解决方案

1. 修改数据库查询函数,传递错误到Express中间件

Express路由处理函数可以通过next(error)将错误交给全局错误中间件处理,替代直接throw error(回调里的throw会导致应用崩溃)。修改查询函数:

const getBoxScoreTraditionalHome = (request, response, next) => { // 添加next参数
    const {playerid, season} = request.params;
 
    let newSeason = JSON.stringify(season);
    let stringSeason = newSeason.replace(/"/g, '');
    db.query(`SELECT * FROM "boxscorestraditional${stringSeason}" 
              INNER JOIN "boxscoresummary${stringSeason}" 
              ON "boxscorestraditional${stringSeason}".game_id = "boxscoresummary${stringSeason}".game_id
              WHERE player_id = $1
              AND "boxscoresummary${stringSeason}".home_team_id = "boxscorestraditional${stringSeason}".team_id
              ORDER BY "boxscorestraditional${stringSeason}".id`, [playerid], (error, results) => {
      if (error) {
         return next(error); // 传递错误给中间件,而非throw
      }
      response.status(200).json(results.rows)
    })
}

2. 配置全局错误处理中间件

在Express主文件(如app.js)中添加全局错误中间件,统一处理所有后端错误并返回规范响应:

// 全局错误处理中间件,必须包含4个参数(err, req, res, next)
app.use((err, req, res, next) => {
    console.error('服务器错误详情:', err);
    // 返回统一格式的错误响应,生产环境可隐藏具体错误信息
    res.status(500).json({
        error: true,
        message: '服务器处理请求时出错',
        details: process.env.NODE_ENV === 'development' ? err.message : undefined
    });
});

3. 修复前端错误捕获逻辑

当前getJsonResponse仅处理了response.json()的异常,未覆盖后端返回错误状态码的情况,修改后确保捕获所有请求错误:

const getJsonResponse = async (url) => {
    console.log(url);
    try {
        const response = await fetch(url);
        if (!response.ok) {
            // 解析后端返回的错误信息,无则使用默认提示
            const errorData = await response.json().catch(() => ({ message: '请求失败' }));
            throw new Error(`请求错误: ${response.status} - ${errorData.message || '未知错误'}`);
        }
        return await response.json();
    } catch(err){
        console.log(err);
        // 重新抛出错误,让上层调用者处理
        throw err;
    }
}

4. 前端调用层添加错误处理

在getSeasonStatAvgLocal中捕获错误,给用户返回友好提示或默认值:

const getSeasonStatAvgLocal = async(stat, year, playerId, H_or_V) => {
    try {
        let url;

        if (H_or_V === 'home') {
            url = '/boxScoresTraditional/home/' + playerId + '/' + year; 
        } else {
            url = '/boxScoresTraditional/visitor/' + playerId + '/' + year; 
        } 
        let gameDetailsArray = await getJsonResponse(url);
        let statTotal = await getSeasonTotalOfStat(stat.toLowerCase(), gameDetailsArray);

        let gamesPlayed = gameDetailsArray.length;
        if (gamesPlayed === 0) {
            throw new Error('该赛季无有效比赛数据');
        }
        let statAverage = statTotal / gamesPlayed;

        return Number.parseFloat(statAverage).toFixed(2);
    } catch (err) {
        console.error('计算场均数据出错:', err);
        // 返回友好提示或默认值
        return 'N/A';
    }
}

额外优化:用async/await重构数据库查询

将数据库查询改为async/await形式,统一用try/catch捕获错误,避免回调嵌套:

const { promisify } = require('util');
// 包装db.query为支持promise的函数(如果原生不支持)
const dbQuery = promisify(db.query);

const getBoxScoreTraditionalHome = async (request, response, next) => {
    try {
        const {playerid, season} = request.params;
 
        let newSeason = JSON.stringify(season);
        let stringSeason = newSeason.replace(/"/g, '');
        const results = await dbQuery(`SELECT * FROM "boxscorestraditional${stringSeason}" 
                  INNER JOIN "boxscoresummary${stringSeason}" 
                  ON "boxscorestraditional${stringSeason}".game_id = "boxscoresummary${stringSeason}".game_id
                  WHERE player_id = $1
                  AND "boxscoresummary${stringSeason}".home_team_id = "boxscorestraditional${stringSeason}".team_id
                  ORDER BY "boxscorestraditional${stringSeason}".id`, [playerid]);
        
        response.status(200).json(results.rows);
    } catch (error) {
        next(error); // 传递错误给全局中间件
    }
}

内容的提问来源于stack exchange,提问作者jack

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 20:25:27