📜  Express 中的错误处理

📅  最后修改于: 2022-05-13 01:56:43.063000             🧑  作者: Mango

Express 中的错误处理

Express 中的错误处理被称为处理或处理在执行任何同步代码或异步代码时可能出现的错误。

同步或异步代码是什么意思?
很多时候,操作开始执行但由于某种原因在完成之前面临一些延迟。此类操作的常见示例是 HTTP 请求(如AJAX 请求)、 setTimeout等函数。这些操作开始,然后在响应返回或计时器结束时结束。当计算机等待这些操作完成时,它会继续忙于下一行代码。它一直很忙,但这会带来一个重大挑战——任何依赖于先前异步代码的代码都可能在该异步代码完成之前运行,这意味着错误。看看下面——

var data = makeAsyncRequest();
  
// Data is undefined
console.log("Data is " + data);


我们可以说,当我们同步执行某件事时,我们会等待它完成,然后再继续执行另一个任务。当我们异步执行某些事情时,我们可以在它完成之前继续执行另一个任务。上述问题可以使用回调中间件模块或使用现代的promise 和 await 来解决。

在 Express 中捕获错误

  • 如果具有路由处理程序和中间件的同步代码抛出任何错误,那么无需任何努力和额外的工作,Express 通过捕获和处理错误来解决它,而无需我们的任何同意。看看下面的代码——
    app.get('/', function (req, res) {
        
        // Express catches this on its own
        throw new Error('Died')
     })
    
  • 如果路由处理程序和中间件调用异步函数进而产生一些错误,那么我们必须显式地将错误传递给 next()函数,Express 将在其中捕获并处理它们。下图将帮助您理解
    app.get('/', function (req, res, next) {
      fs.readFile('/file-is-not-available', 
            function (err, data) {
        if (err) {
      
          // Passing errors to 
          // Express explicitly
          next(err) 
        } else {
          res.send(data)
        }
      })
    })
    
  • 返回 Promise 的路由处理程序和中间件将在拒绝或抛出错误时自动调用 next(value)。
    app.get('/user/:id', async function (req, res, next) { 
      
        // Async keyword tells that it is
        // an asynchronous function
        var user = await getUserById(req.params.id)    
        res.send(user)
    })
    

    await 关键字可用于指示后面的函数将返回一个 Promise,在执行任何其他相关代码之前应该等待它。 'await' 只能在异步函数中使用。
    如果getUserById抛出错误或拒绝,将使用抛出的错误或拒绝的值调用 Next (next)。如果没有提供拒绝值,next 将使用 Express 路由器提供的默认错误对象调用。如果我们将任何内容传递给 next()函数(字符串'route' 除外),Express 会将当前请求视为错误,并将跳过任何剩余的非错误处理路由和中间件函数。

  • 如果序列中的给定回调不提供数据而只提供错误,则代码可以简化为 -
    app.get('/', [
      function (req, res, next) {
        fs.writeFile('/path-cannot-be-accessed',
                'data', next)
      },
      function (req, res) {
        res.send('OK')
      }
    ])
    

    在上面的代码中, next作为回调提供,它运行时不关心是否出现错误。如果没有错误,那么第二个处理程序也会运行,否则 express 只是捕获并处理错误。

    现在,看看下面的例子——

    app.get('/', function (req, res, next) {
      setTimeout(function () {
        try {
          throw new Error('Died')
        } catch (err) {
          next(err)
        }
      }, 100)
    })
    
  • 正如我们所知,如果路由处理程序和中间件调用异步函数进而产生一些错误,那么我们必须显式地将错误传递给 next()函数,Express 将在其中捕获并处理它们。但是,在上面的代码中,错误不是同步代码的部分,所以我们不能简单地将它传递给下一个函数。我们需要先抛出错误,捕获异步代码产生的错误,然后将其传递给 Express。为此,我们需要使用try..catch块来捕获它们。如果您不想使用 try 和 catch,那么只需使用Promise ,如下所示 -
    app.get('/', function (req, res, next) {
      Promise.resolve().then(function () {
        throw new Error('Died')
      
      // Errors will be passed to Express
      }).catch(next)
    })
    

    由于 Promise 自动捕获同步错误和被拒绝的 Promise,您可以简单地提供 next 作为最终的 catch 处理程序,Express 将捕获错误,因为 catch 处理程序将错误作为第一个参数。

  • 默认错误处理程序:默认错误处理程序在我们调用时捕获错误 接下来,不要使用自定义错误处理程序来处理它。如果我们想向默认值发送不同的响应,我们必须编写自己的错误处理程序。这个默认的错误处理中间件函数被添加到中间件函数栈的末尾。如果您将错误传递给 next() 并且您没有在自定义错误处理程序中处理它,它将由内置错误处理程序处理,错误将使用堆栈跟踪写入客户端。堆栈跟踪不包含在生产环境中。

    写入错误时,会自动将以下信息添加到响应中:

    • res.statusCode 是从 err.status(或 err.statusCode)设置的。如果此值超出 4xx 或 5xx 范围,则将设置为 500。
    • res.statusMessage 根据状态码设置。
    • 在生产环境中,body 为状态码消息的 HTML,否则为 err.stack。
    • err.headers 对象中指定的任何标头。

    如果在开始编写响应后调用 next() 时出现错误(例如,如果在将响应流式传输到客户端时遇到错误),Express 默认错误处理程序将关闭连接并使请求失败。

    因此,当您添加自定义错误处理程序时,您必须委托默认的 Express 错误处理程序,此时标头已发送到客户端:

    function errorHandler (err, req, res, next) {
      if (res.headersSent) {
        return next(err)
      }
      res.status(500)
      res.render('error', { error: err })
    }
    

    请注意,如果您多次调用 next() 并在代码中出现错误,则可能会触发默认错误处理程序,即使已使用自定义错误处理中间件也是如此。

    如何编写错误处理程序?

    我们声明中间件函数的方式,同样定义错误处理函数。然而,错误处理函数有四个参数而不是三个: (err, req, res, next)。例如 -

    app.use(function (err, req, res, next) {
      console.error(err.stack)
      res.status(500).send('Something broke!')
    })
    

    我们需要在其他 app.use() 和路由调用之后最后定义错误处理中间件。示例如下所示——

    app.get('/', (req, res, next) => {
     req.foo = true;
      setTimeout(() => {
        try {
          throw new Error('error');
        }
        catch (ex) {
          next(ex);
        }
      })
    });
    app.use((err, req, res, next) => {
      if (req.foo) {
        res.status(500).send('Fail!');
      }
      else {
        next(err);
      }
    })
    app.use((err, req, res, next) => {
      res.status(500).send('Error!')
    })