Express中的错误处理

当我第一次开始使用Express并试图弄清楚如何处理错误时,我很难受。 感觉没有人写我需要的东西。 结果,我不得不自己寻找问题的答案。 今天,我想告诉我有关Express应用程序中错误处理的所有知识。 让我们从同步错误开始。



同步错误处理


如果需要处理同步错误,则可以首先使用throw语句在Express请求处理程序中throw此类错误。 请注意,请求处理程序也称为“控制器”,但我更喜欢使用“请求处理程序”一词,因为在我看来,这更清楚。

看起来是这样的:

 app.post('/testing', (req, res) => {  throw new Error('Something broke! ') }) 

可以使用Express错误处理程序捕获此类错误。 如果您尚未编写自己的错误处理程序(我们将在下文中详细讨论),则Express将使用默认处理程序来处理错误。

这是标准Express错误处理程序的作用:

  1. 将HTTP响应状态代码设置为500。
  2. 向执行请求的实体发送文本响应。
  3. 将文本响应记录到控制台。


控制台中显示错误消息

异步错误处理


要处理异步错误,您需要通过next参数将错误发送到Express错误处理程序:

 app.post('/testing', async (req, res, next) => {  return next(new Error('Something broke again! ')) }) 

这是记录此错误时进入控制台的内容。


控制台中显示错误消息

如果在Express应用程序中使用async / await构造,则需要使用包装函数,例如express-async-handler 。 这使您可以编写异步代码而无需try / catch块。 在此处阅读有关Express中异步/等待的更多信息。

 const asyncHandler = require('express-async-handler') app.post('/testing', asyncHandler(async (req, res, next) => {  //  - })) 

将请求处理程序包装在express-async-handler ,您可以如上所述使用throw语句引发错误。 该错误将转到Express错误处理程序。

 app.post('/testing', asyncHandler(async (req, res, next) => {  throw new Error('Something broke yet again! ') })) 


控制台中显示错误消息

编写自己的错误处理程序


Express错误处理程序采用4个参数:

  1. 错误
  2. 要求
  3. 资源
  4. 下一个

您需要将它们放置在中间处理程序和路由之后。

 app.use(/*...*/) app.get(/*...*/) app.post(/*...*/) app.put(/*...*/) app.delete(/*...*/) //           app.use((error, req, res, next) => { /* ... */ }) 

如果您创建自己的错误处理程序,Express将停止使用标准错误处理程序。 为了处理错误,您需要为前端应用程序生成一个响应,该响应对发生错误的端点进行了寻址。 这意味着您需要执行以下操作:

  1. 生成并发送适当的响应状态代码。
  2. 形成并发送适当的答案。

在每种情况下哪种状态代码合适,取决于发生了什么。 这是您应该准备处理的常见错误列表:

  1. 错误400 Bad Request 。 在两种情况下使用。 首先,当用户未在请求中包括必填字段时(例如,带有信用卡信息的字段未填写在发送的付款表单中)。 其次,当请求包含不正确的数据时(例如,在密码字段和密码确认字段中输入不同的密码)。
  2. 错误401 Unauthorized 。 如果用户输入了错误的凭据(例如用户名,电子邮件地址或密码),则将应用此响应状态代码。
  3. 错误403 Forbidden 。 在不允许用户访问端点时使用。
  4. 错误404 Not Found 。 在无法检测到端点的情况下使用它。
  5. 错误500 Internal Server Error 。 当前端发送的请求正确构成,但后端发生一些错误时,将应用此方法。

定义适当的响应状态代码后,必须使用res.status进行设置:

 app.use((error, req, res, next) => {  // ,         res.status(400)  res.json(/* ... */) }) 

响应状态代码应对应于错误消息。 为此,将状态代码与错误一起发送。

最简单的方法是使用http-errors包。 它允许错误地发送三段信息:

  1. 响应状态码。
  2. 与错误关联的消息。
  3. 需要发送的任何数据(这是可选的)。

以下是安装http-errors软件包的方法:

 npm install http-errors --save 

这是使用此软件包的方法:

 const createError = require('http-errors') //   throw createError(status, message, properties) 

考虑一个示例,它将使您正确地理解所有这一切。

想象一下,我们正在尝试在其电子邮件地址中找到用户。 但是找不到该用户。 因此,我们决定响应相应的请求,发送“ User not found错误,通知调用者未找到该用户。

这是创建错误时我们需要做的:

  1. 将响应状态代码设置为400 Bad Request (毕竟,用户输入了错误的数据)。 这将是我们的第一个参数。
  2. 将消息发送给呼叫者,例如“ User not found 。 这将是第二个参数。

 app.put('/testing', asyncHandler(async (req, res) => {  const { email } = req.body  const user = await User.findOne({ email })  //     -    if (!user) throw createError(400, `User '${email}' not found`) })) 

您可以使用error.status构造获取状态代码,并使用error.message获取错误消息:

 //   app.use((error, req, res, next) => {  console.log('Error status: ', error.status)  console.log('Message: ', error.message) }) 


控制台中记录错误的结果

然后使用res.status设置响应状态,并将消息写入res.json

 app.use((error, req, res, next) => {  //      res.status(error.status)  //    res.json({ message: error.message }) }) 

就我个人而言,我更喜欢在此类响应中发送状态代码,消息和堆栈跟踪结果。 这使调试更加容易。

 app.use((error, req, res, next) => {  //      res.status(error.status)  //    res.json({    status: error.status,    message: error.message,    stack: error.stack  }) }) 

▍默认响应状态码


如果错误的来源不是createError ,则它将没有status属性。 这是一个尝试使用fs.readFile读取不存在的文件的fs.readFile

 const fs = require('fs') const util = require('util') //  readFile  ,  ,  async/await-. //     : https://zellwk.com/blog/callbacks-to-promises const readFilePromise = util.promisify(fs.readFile) app.get('/testing', asyncHandler(async (req, res, next) => {  const data = await readFilePromise('some-file') }) 

这样的错误对象将不具有status属性:

 app.use((error, req, res, next) => {  console.log('Error status: ', error.status)  console.log('Message: ', error.message) }) 


控制台中记录错误的结果

在这种情况下,您可以设置默认错误代码。 即,我们正在谈论500 Internal Server Error

 app.use((error, req, res, next) => {  res.status(error.status || 500)  res.json({    status: error.status,    message: error.message,    stack: error.stack  }) }) 

▍更改错误状态代码


假设我们将使用用户提供的数据读取某个文件。 如果这样的文件不存在,则意味着我们需要给出一个400 Bad Request错误。 毕竟,找不到服务器,因为找不到文件。

在这种情况下,您需要使用try / catch构造来捕获原始错误。 然后,您需要使用createError重新创建错误对象:

 app.get('/testing', asyncHandler(async (req, res, next) => {  try {    const { file } = req.body    const contents = await readFilePromise(path.join(__dirname, file))  } catch (error) {    throw createError(400, `File ${file} does not exist`)  } }) 

▍404错误处理


如果该请求经过所有中间处理程序和路由,但未处理,则意味着找不到与该请求相对应的端点。

要处理404 Not Found错误,您需要在路由和错误处理程序之间添加一个附加处理程序。 404错误对象的创建如下所示:

 //  ... // ... app.use((req, res, next) => {  next(createError(404)) }) //  ... 


错误详情

▍ERR_HTTP_HEADERS_SENT错误说明


如果看到错误消息ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client请不要惊慌ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client 。 之所以会出现这种情况,是因为在同一处理程序中反复调用了设置响应头的方法。 以下是调用这些方法以自动设置响应头的方法:

  1. 重新发送
  2. res.json
  3. 重新渲染
  4. res.sendFile
  5. res.sendStatus
  6. 结束
  7. 重定向

因此,例如,如果在同一响应处理程序中调用res.renderres.json ,则会收到错误ERR_HTTP_HEADERS_SENT

 app.get('/testing', (req, res) => {  res.render('new-page')  res.json({ message: '¯\_(ツ)_/¯' }) }) 

因此,如果遇到此错误,请仔细检查响应处理程序代码,并确保在任何情况下都不会调用上述几种方法。

▍错误处理和数据流


如果将响应流传输到前端时出了点问题,那么您可能会遇到相同的错误ERR_HTTP_HEADERS_SENT

在这种情况下,必须将错误处理传递给标准处理程序。 这样的处理程序将发送错误并自动关闭连接。

 app.use((error, req, res, next) => {  //       ,        if (res.headersSent) {    return next(error)  }  //     }) 

总结


今天,我告诉您有关Express中错误处理的所有知识。 我希望这可以帮助您编写更可靠的Express应用程序。

亲爱的读者们! 您如何处理Node.js项目中的错误?


Source: https://habr.com/ru/post/zh-CN476290/


All Articles