【问题标题】:Propper way of Error handling in a nodejs API servernodejs API服务器中错误处理的正确方法
【发布时间】:2021-06-16 10:16:50
【问题描述】:

我需要在 node.js 上处理我的 API 服务器中的错误。 我创建了一个错误处理模块,它将错误(仅在开发模式下)以类似于以下的 JSON 对象发送到 API 客户端:

{
    "status": "fail",
    "error": {
        "statusCode": 404,
        "status": "fail",
        "isOperational": true
    },
    "message": "no valid register found. Please provide a valid register",
    "stack": "Error: no valid register found. Please provide a valid register\n    at /Users/myUser/ITstuff/smarthome-heating/controllers/modbusController.js:77:21\n    at async /Users/myUser/ITstuff/smarthome-heating/controllers/modbusController.js:73:17"
}

现在我遇到的问题是,当 switch 语句中没有 case 为真时,我需要在我的 API 控制器的子模块中创建一个新错误。

// this is the modbusHandler.setValue(doc, val, next) function
// 2) RUN PROGRAMMS
  let checkVal;
  switch (doc.register) {
    case 0:
      await client.writeCoil(doc.address + offset, !!val);
      checkVal = await client.readCoils(doc.address + offset, 1);
      break;
    case 4:
      await client.writeRegister(doc.address + offset, val);
      checkVal = await client.readHoldingRegisters(doc.address + offset, 1);
      break;
    default:
      throw new Error(
    'no valid register found. Please provide a valid register'
  );
  }

这个时候,我是这样处理的:

// the function is wrapped in a try-catch statement
     const val = await modbusHandler.setValue(doc, req.body.value, next).catch((err) => {
                    console.log('here it is');
                    return next(new AppError(err.message, 404));
                  });


res.status(200).json({
    status: 'success',
    data: {
      val,
    },
  });
  1. 在 API 控制器中,调用带有 switch 语句的函数
  2. 如果没有大小写与表达式匹配,则会引发新的错误
  3. 捕获错误然后调用自定义错误并以 JSON 格式向客户端响应错误

这个解决方案并不真正有效,我收到响应错误,因为 API 控制器的功能没有返回。这会导致第二次响应,这当然不好。

我现在的问题是:我怎样才能以正确的方式解决这个问题?

自定义错误构造函数:

class AppError extends Error {
  constructor(message, statusCode) {
    // this is the official error message rom the error it self, this message will be in the response.json for the client
    super(message);

    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true;

    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = AppError;

自定义错误处理程序:

module.exports = (err, req, res, next) => {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';

  if (process.env.NODE_ENV === 'development') {
    sendErrDev(err, res);
  } else if (process.env.NODE_ENV === 'production') {
    sendErrProd(err, res);
  }
};


const sendErrDev = (err, res) => {
  res.status(err.statusCode).json({
    status: err.status,
    error: err,
    message: err.message,
    stack: err.stack,
  });
};

【问题讨论】:

  • 如果您提供更多有关自定义错误处理程序的上下文,您可能能够更快地获得帮助。正如所写,看起来您的错误处理程序被用作中间件,这意味着当它实际到达 api 路由处理程序时,您的自定义错误处理程序已经发生了。也许显示你实际上在哪里 catching 错误
  • 在 API 控制器中的函数调用之后,我使用 .catchswitch 语句中捕获了错误。在这个.catch 中,我返回一个新的自定义错误,它为客户端提供了一个 API 响应
  • 是的,我的错误处理程序被用作中间件app.use(AppError); 也许我需要提一下,整个 API 控制器函数被包装在一个 try-catch 块中,其中 catch 简单地调用 next
  • 因此,如果您使用的是 express 的内置路由功能,我认为是这种情况,问题是您的错误处理中间件在代码到达您的 API 之前就已运行控制器,所以当控制器抛出错误时,它永远不会回到中间件,如果这有意义的话。
  • 据我了解,它是这样的:在modbusHandler.setValue() 中,switch 语句恰好是默认值,这会引发错误。这个错误在函数被调用的地方被捕获并执行return next(new AppError(...,这是一个全局错误处理程序,它将以 JSON 格式为客户端提供响应。问题是代码在函数调用之后向前运行并到达响应的位置,这会导致第二个错误,因为 AppError 已经发送了响应。这正是我试图解决的第二个错误。

标签: javascript node.js rest express


【解决方案1】:

与其抛出错误,不如像这样将其抛出到下一个,以便错误处理程序可以处理它。

export const yourFunction = async (req, res, next) => {
   .....
   // throw the error to your route Error handler 
   return next(new AppError('no valid register found. Please provide a valid register', 400)) 
}

那么在你声明完所有路由之后,你应该有一个看起来像这样的错误处理程序。

app.use(ErrorHandler);

您可能还需要一个错误捕获器

//catchAsync
module.exports = fn => {
    return (req, res, next) => {
      fn(req, res, next).catch(next);
    };
  };

你会像这样包裹你的路线。

route.get('/path/', catchAsync(yourFunction))

如果您创建了 catchAsync 中间件,则您的路由中根本不需要任何 Try/Catch,因为所有这些都将被扔给您的错误处理程序。

更新。

关于节点的小抱怨,甚至 javascript 都是关于错误处理的。对于被调用函数的每一个函数,如果选择抛出错误,则需要抛出error of error的错误。你不断冒泡“抛出错误”,然后它就会失控。

在你的 switch 语句中,我建议你返回一个 null。

然后你测试`if (!variable) return next(new AppError);

辅助函数的行为应该像辅助函数,它应该返回 true/false/null/value,然后您在主函数中确定是否应该抛出错误。

这样你可以集中你的错误。

【讨论】:

  • 我完全按照你说的做了。但我仍然得到错误:Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client 这是因为错误被抛出到全局错误处理程序app.use(ErrorHandler) 的函数在路由的函数中。所以我永远不会在路线上退出功能。这会导致问题,即路由上的函数进一步响应响应并希望将响应发送给客户端。还有我得到标题错误的地方。不知何故,我需要摆脱路线上的功能......
猜你喜欢
  • 1970-01-01
  • 2022-12-31
  • 2021-07-02
  • 2014-02-10
  • 2017-08-31
  • 2014-09-02
  • 2012-10-06
  • 2019-05-17
  • 1970-01-01
相关资源
最近更新 更多