前端开发记录:Node项目接口异常捕获

问题背景

在迭代一个基于 Node.jsExpress 的Web应用时,遇到一个影响用户体验的问题:当第三方接口不可用或返回异常时,整个页面会直接跳转到500错误白屏页面,导致用户无法继续使用应用。

var express = require('express')
var app = express()
app.use(function (err, req, res, next) {
  logger.error(`[500] method: ${req.method}, url: ${req.url}`);
  res.status(500).render('500.html',{
    title:'500'
  });
});

请求代码如下:

// controller
var request = require("request");
var express = require("express")
var homeRouter = express.Router();
homeRouter.post("/", function (req, res) {
  request.get({ url: '/demo' }, function (error, response, body) {
    // ***逻辑处理
  })
})
存在问题

1、异常处理缺失:当第三方接口请求失败(网络问题、超时、接口返回5xx等)时,代码直接抛出错误,触发Express的错误处理中间件,导致页面跳转到500错误页
2、缺乏默认值处理:即使接口返回错误数据,也没有提供合理的默认值,导致前端无法正常显示
3、日志记录不完善:没有对请求的异常情况进行有效记录,不利于问题排查

解决方案

设计并实现了一个safeRequest工具函数,用于封装第三方接口请求,提供以下关键功能:
1、统一错误处理:对网络错误、服务端错误、客户端错误进行分类处理
2、默认值返回:当请求失败时返回默认值,避免页面白屏
3、详细日志记录:记录请求的详细信息,便于问题排查
4、超时控制:添加合理的请求超时设置,防止请求挂起

const request = require('request');
const logger = require('./logger');

/**
 * 请求封装,对请求结果进行统一处理,并返回接口数据或默认值
 * @param {object} options 请求参数
 * @param {object} defaultValue 默认值
 * @param {function} callback 回调函数
 */
module.exports = function safeRequest(options, defaultValue, callback) {
  // 支持只传两个参数:options, callback(第三个参数可选)
  if (typeof defaultValue === 'function') {
    callback = defaultValue;
    defaultValue = null;
  }
  
  const startTime = Date.now();
  // 默认值
  const finalDefaultValue = defaultValue || null;
  
  // 添加默认超时 (60秒)
  const reqOptions = Object.assign({ timeout: 60 * 1000 }, options);
  
  request(reqOptions, (error, response, body) => {
    const duration = Date.now() - startTime;
    const url = options.url || 'unknown';
    
    if (error) {
      logger.warn(`[safeRequest] 请求错误: ${url},耗时: ${duration}ms, 错误:`, error);
      return callback(null, finalDefaultValue);
    }
    
    if (response.statusCode >= 500) {
      logger.warn(`[safeRequest] 服务端错误: ${url},状态码: ${response.statusCode},耗时: ${duration}ms`);
      return callback(null, finalDefaultValue);
    }
    
    if (response.statusCode >= 400) {
      logger.info(`[safeRequest] 客户端错误: ${url},状态码: ${response.statusCode}`);
      // 4xx 也返回默认值,不中断流程
      return callback(null, finalDefaultValue);
    }
    
    // 尝试解析 JSON
    try {
      const data = JSON.parse(body);
      // !!!注意:此处需根据实际接口返回状态进行修改
      if (data.code === 200 || data.code === 8200 || data.success === true) {
        logger.info(`[safeRequest] 请求成功: ${url},耗时: ${duration}ms`);
        return callback(null, data);
      } else {
        logger.warn(`[safeRequest] 请求失败: ${url},状态码: ${response.code},耗时: ${duration}ms`);
        return callback(null, finalDefaultValue);
      }
    } catch (e) {
      logger.warn(`[safeRequest] 响应解析失败: ${url},响应内容:`, body);
      callback(null, finalDefaultValue);
    }
  });
};
使用示例
const safeRequest = require('../utils/safeRequest');
homeRouter.post("/", function (req, res) {
  safeRequest({   url: '/demo'  }, { data: [] }, function (error, resData) {
    // ***逻辑处理
  });
});
问题引申思考:

一、为什么 Express 错误处理中间件无法像 Axios 响应拦截器一样工作?

// Axios响应拦截器
axios.interceptors.response.use(
  response => {
    // 处理成功响应
    return response;
  },
  error => {
    // 处理错误响应
    return Promise.reject(error);
  }
);

Axios 的拦截器机制是基于 Promise 实现的,可以在请求完成(无论成功或失败)后,自动触发拦截器,实现对响应的统一处理。而在该项目中,当 request.get 发生错误时,错误对象 error 被传递到回调函数,但没有调用 next(error),因此 Express 的错误处理中间件不会被触发。但代码继续执行,比如:JSON.parse(body),由于 body 可能为 undefined 或无效数据,导致新的错误,最终触发 Express 的错误处理中间件,则被 app (即express()实例)捕获。
总结:
触发机制不同:Axios 的拦截器是"自动触发"的,而 Express 的错误处理中间件需要显式调用next(err)才能触发
错误传递方式不同:Axios 通过 Promise 链自动传递错误,Express 需要手动传递错误

二、为什么不能简单地在回调中调用 next(error)?

// 尝试在回调中调用 next(error):
request.get({ url: '/demo' }, function (error, response, body) {
  if (error) {
    return next(error); // 试图触发错误处理
  }
  res.json(JSON.parse(body));
});

但这样会引发另一个问题:当错误发生时,已经调用了 next(error),Express 会将请求交给错误处理中间件,而错误处理中间件会调用 res.status(500).render('500.html'),导致页面跳转到500页。这恰恰是此次需求变更要求避免的,因此需要采取调用封装函数进行异常捕获处理。

链接汇总

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容