Error Handling错误处理

Error Handling refers to how Express catches and processes errors that occur both synchronously and asynchronously. 错误处理是指Express如何捕获和处理同步和异步发生的错误。Express comes with a default error handler so you don’t need to write your own to get started.Express附带了一个默认的错误处理程序,因此您不需要自己编写错误处理程序即可开始。

Catching Errors捕捉错误

It’s important to ensure that Express catches all errors that occur while running route handlers and middleware.确保Express捕获运行路由处理程序和中间件时发生的所有错误非常重要。

Errors that occur in synchronous code inside route handlers and middleware require no extra work. If synchronous code throws an error, then Express will catch and process it. For example:路由处理程序和中间件中同步代码中发生的错误不需要额外的工作。如果同步代码抛出错误,Express将捕获并处理它。例如:

app.get('/', (req, res) => {
  throw new Error('BROKEN') // Express will catch this on its own.
})

For errors returned from asynchronous functions invoked by route handlers and middleware, you must pass them to the next() function, where Express will catch and process them. For example:对于路由句柄和中间件调用的异步函数返回的错误,您必须将其传递给next()函数,Express将在其中捕获并处理它们。例如:

app.get('/', (req, res, next) => {
  fs.readFile('/file-does-not-exist', (err, data) => {
    if (err) {
      next(err) // Pass errors to Express.
    } else {
      res.send(data)
    }
  })
})

Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error.从Express 5开始,当路由处理程序和中间件拒绝或抛出错误时,它们会自动调用next(值)。 For example:例如:

app.get('/user/:id', async (req, res, next) => {
  const user = await getUserById(req.params.id)
  res.send(user)
})

If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. 如果getUserById抛出错误或拒绝,则next将调用抛出的错误或拒绝的值。If no rejected value is provided, next will be called with a default Error object provided by the Express router.如果没有提供拒绝值,则将使用Express路由器提供的默认Error对象调用next

If you pass anything to the next() function (except the string 'route'), Express regards the current request as being an error and will skip any remaining non-error handling routing and middleware functions.如果你向next()函数传递任何内容(字符串'route'除外),Express会将当前请求视为错误,并跳过任何剩余的非错误处理路由和中间件函数。

If the callback in a sequence provides no data, only errors, you can simplify this code as follows:如果序列中的回调不提供数据,只提供错误,则可以将此代码简化如下:

app.get('/', [
  function (req, res, next) {
    fs.writeFile('/inaccessible-path', 'data', next)
  },
  function (req, res) {
    res.send('OK')
  }
])

In the above example, next is provided as the callback for fs.writeFile, which is called with or without errors. 在上面的示例中,next作为fs.writeFile的回调函数提供,无论是否有错误,都会调用它。If there is no error, the second handler is executed, otherwise Express catches and processes the error.如果没有错误,则执行第二个处理程序,否则Express会捕获并处理错误。

You must catch errors that occur in asynchronous code invoked by route handlers or middleware and pass them to Express for processing. For example:您必须捕获路由处理程序或中间件调用的异步代码中出现的错误,并将其传递给Express进行处理。例如:

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('BROKEN')
    } catch (err) {
      next(err)
    }
  }, 100)
})

The above example uses a try...catch block to catch errors in the asynchronous code and pass them to Express. 上面的例子使用了try...catch块用于捕获同步代码中的错误并将其传递给Express。If the try...catch block were omitted, Express would not catch the error since it is not part of the synchronous handler code.如果try...catch块被省略,Express将无法捕获错误,因为它不是同步处理程序代码的一部分。

Use promises to avoid the overhead of the try...catch block or when using functions that return promises. For example:使用promises来避免try...catch块的开销或使用返回promise的函数时。例如:

app.get('/', (req, res, next) => {
  Promise.resolve().then(() => {
    throw new Error('BROKEN')
  }).catch(next) // Errors will be passed to Express.
})

Since promises automatically catch both synchronous errors and rejected promises, you can simply provide next as the final catch handler and Express will catch errors, because the catch handler is given the error as the first argument.由于promise会自动捕获同步错误和被拒绝的promise,因此您可以简单地提供next作为最终的catch处理程序,Express将捕获错误,因为catch处理程序将错误作为第一个参数。

You could also use a chain of handlers to rely on synchronous error catching, by reducing the asynchronous code to something trivial. For example:您还可以使用一系列处理程序来依赖同步错误捕获,将异步代码简化为微不足道的东西。例如:

app.get('/', [
  function (req, res, next) {
    fs.readFile('/maybe-valid-file', 'utf-8', (err, data) => {
      res.locals.data = data
      next(err)
    })
  },
  function (req, res) {
    res.locals.data = res.locals.data.split(',')[1]
    res.send(res.locals.data)
  }
])

The above example has a couple of trivial statements from the readFile call. 上面的例子有readFile调用中的几个琐碎语句。If readFile causes an error, then it passes the error to Express, otherwise you quickly return to the world of synchronous error handling in the next handler in the chain. 如果readFile导致错误,则它会将错误传递给Express,否则您将很快返回到链中下一个句柄中的同步错误处理世界。Then, the example above tries to process the data. If this fails, then the synchronous error handler will catch it. 然后,上面的例子试图处理数据。如果此操作失败,则同步错误处理程序将捕获它。If you had done this processing inside the readFile callback, then the application might exit and the Express error handlers would not run.如果您在readFile回调中完成了此处理,则应用程序可能会退出,Express错误处理程序将不会运行。

Whichever method you use, if you want Express error handlers to be called in and the application to survive, you must ensure that Express receives the error.无论使用哪种方法,如果希望调用Express错误处理程序并使应用程序存活,则必须确保Express收到错误。

The default error handler默认错误处理程序

Express comes with a built-in error handler that takes care of any errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack.Express附带了一个内置的错误处理程序,可以处理应用程序中可能遇到的任何错误。此默认错误处理中间件功能添加在中间件功能堆栈的末尾。

If you pass an error to next() and you do not handle it in a custom error handler, it will be handled by the built-in error handler; the error will be written to the client with the stack trace. 如果你将错误传递给next(),而你没有在自定义错误处理程序中处理它,它将由内置的错误处理程序处理;错误将通过堆栈跟踪写入客户端。The stack trace is not included in the production environment.生产环境中不包括堆栈跟踪。

Set the environment variable NODE_ENV to production, to run the app in production mode.将环境变量NODE_ENV设置为production,以在生产模式下运行应用程序。

When an error is written, the following information is added to the response:当写入错误时,以下信息将添加到响应中:

If you call next() with an error after you have started writing the response (for example, if you encounter an error while streaming the response to the client), the Express default error handler closes the connection and fails the request.如果在开始写入响应后调用next()时出错(例如,如果在将响应流式传输到客户端时遇到错误),Express默认错误处理程序将关闭连接并使请求失败。

So when you add a custom error handler, you must delegate to the default Express error handler, when the headers have already been sent to the client:因此,当您添加自定义错误处理程序时,当标头已经发送到客户端时,您必须委托给默认的Express错误处理程序:

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

Note that the default error handler can get triggered if you call next() with an error in your code more than once, even if custom error handling middleware is in place.请注意,如果您在代码中多次调用带有错误的next(),即使有自定义错误处理中间件,默认错误处理程序也可能被触发。

Other error handling middleware can be found at Express middleware.其他错误处理中间件可以在Express中间件中找到。

Writing error handlers编写错误处理程序

Define error-handling middleware functions in the same way as other middleware functions, except error-handling functions have four arguments instead of three: (err, req, res, next). For example:以与其他中间件函数相同的方式定义错误处理中间件函数,除了错误处理函数有四个参数而不是三个:(err, req, res, next)。例如:

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

You define error-handling middleware last, after other app.use() and routes calls; for example:在其他app.use()和路由调用之后,最后定义错误处理中间件;例如:

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use((err, req, res, next) => {
  // logic
})

Responses from within a middleware function can be in any format, such as an HTML error page, a simple message, or a JSON string.来自中间件函数的响应可以是任何格式,例如HTML错误页面、简单消息或JSON字符串。

For organizational (and higher-level framework) purposes, you can define several error-handling middleware functions, much as you would with regular middleware functions. 出于组织(和更高级别的框架)的目的,您可以定义几个错误处理中间件功能,就像使用常规中间件功能一样。For example, to define an error-handler for requests made by using XHR and those without:例如,要为使用XHR发出的请求和不使用XHR的请求定义错误句柄:

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)

In this example, the generic logErrors might write request and error information to stderr, for example:在此示例中,通用logErrors可能会将请求和错误信息写入stderr,例如:

function logErrors (err, req, res, next) {
  console.error(err.stack)
  next(err)
}

Also in this example, clientErrorHandler is defined as follows; in this case, the error is explicitly passed along to the next one.同样在这个例子中,clientErrorHandler的定义如下;在这种情况下,错误会明确地传递给下一个错误。

Notice that when not calling “next” in an error-handling function, you are responsible for writing (and ending) the response. Otherwise, those requests will “hang” and will not be eligible for garbage collection.请注意,在错误处理函数中不调用“next”时,您有责任编写(并结束)响应。否则,这些请求将“挂起”,并且没有资格进行垃圾回收。

function clientErrorHandler (err, req, res, next) {
  if (req.xhr) {
    res.status(500).send({ error: 'Something failed!' })
  } else {
    next(err)
  }
}

Implement the “catch-all” errorHandler function as follows (for example):按如下方式实现“catch-all”errorHandler函数(例如):

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

If you have a route handler with multiple callback functions, you can use the route parameter to skip to the next route handler. For example:如果您有一个具有多个回调函数的路由处理程序,则可以使用route参数跳到下一个路由处理程序。例如:

app.get('/a_route_behind_paywall',
  (req, res, next) => {
    if (!req.user.hasPaid) {
      // continue handling this request
      next('route')
    } else {
      next()
    }
  }, (req, res, next) => {
    PaidContent.find((err, doc) => {
      if (err) return next(err)
      res.json(doc)
    })
  })

In this example, the getPaidContent handler will be skipped but any remaining handlers in app for /a_route_behind_paywall would continue to be executed.在这个例子中,getPaidContent处理程序将被跳过,但app/a_route_behind_paywall的任何剩余处理程序都将继续执行。

Calls to next() and next(err) indicate that the current handler is complete and in what state. 调用next()next(err)表示当前处理程序已完成并且处于什么状态。next(err) will skip all remaining handlers in the chain except for those that are set up to handle errors as described above.next(err)将跳过链中所有剩余的处理程序,但如上所述设置为处理错误的处理程序除外。