🌐 Nodejs.cn

错误处理

错误处理 指的是 Express 如何捕获和处理同步和异步发生的错误。Express 自带了默认的错误处理器,因此你不需要自己编写就可以开始使用。

🌐 Error Handling refers to how Express catches and processes errors that occur both synchronously and asynchronously. Express comes with a default error handler so you don’t need to write your own to get started.

捕捉错误

🌐 Catching Errors

确保 Express 捕获在运行路由处理程序和中间件时发生的所有错误是很重要的。

🌐 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:

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

对于由路由处理程序和中间件调用的异步函数返回的错误,你必须将它们传递给 next() 函数,Express 将捕获并处理它们。例如:

🌐 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:

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);
}
});
});

从 Express 5 开始,返回 Promise 的路由处理程序和中间件在拒绝或抛出错误时会自动调用 next(value)。例如:

🌐 Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example:

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

如果 getUserById 抛出错误或被拒绝,next 将被调用,并传入抛出的错误或被拒绝的值。如果没有提供被拒绝的值,next 将被调用,并传入 Express 路由提供的默认错误对象。

🌐 If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.

如果你向 next() 函数传递任何东西(除了字符串 'route'),会将当前请求视为错误,并跳过任何剩余的非错误处理路由和中间件函数。

🌐 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.

如果序列中的回调不提供数据,只提供错误,你可以将此代码简化如下:

🌐 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');
},
]);

在上面的例子中,next 被提供作为 fs.writeFile 的回调,fs.writeFile 会在有错误或没有错误的情况下被调用。如果没有错误,第二个处理器会被执行,否则 Express 会捕获并处理错误。

🌐 In the above example, next is provided as the callback for fs.writeFile, which is called with or without errors. 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:

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

上面的示例使用 try...catch 块来捕获异步代码中的错误并将其传递给 Express。如果省略 try...catch 块,Express 将不会捕获错误,因为它不属于同步处理程序代码的一部分。

🌐 The above example uses a try...catch block to catch errors in the asynchronous code and pass them to Express. If the try...catch block were omitted, Express would not catch the error since it is not part of the synchronous handler code.

使用 promises 来避免 try...catch 块的开销,或者在使用返回 promises 的函数时使用。例如:

🌐 Use promises to avoid the overhead of the try...catch block or when using functions that return promises. For example:

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

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

🌐 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.

你也可以使用一连串的处理器来依赖同步错误捕获,通过将异步代码简化为一些简单的内容。例如:

🌐 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);
},
]);

上面的例子中有几个来自 readFile 调用的琐碎语句。如果 readFile 出现错误,那么它会将错误传递给 Express,否则你会很快回到链中下一个处理程序的同步错误处理世界。然后,上面的例子尝试处理数据。如果处理失败,同步错误处理程序会捕获它。如果你在 readFile 回调中进行这一步处理,那么应用可能会退出,而 Express 错误处理程序将不会运行。

🌐 The above example has a couple of trivial statements from the readFile call. 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. 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.

无论你使用哪种方法,如果你希望调用 Express 错误处理程序并让应用继续运行,你必须确保 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.

默认错误处理程序

🌐 The default error handler

Express 内置了一个错误处理程序,用于处理应用中可能遇到的任何错误。这个默认的错误处理中间件函数会被添加到中间件函数堆栈的末尾。

🌐 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.

如果你将错误传递给 next(),而你没有在自定义错误处理器中处理它,它将由内置错误处理器处理;错误将连同堆栈跟踪一起写入客户端。生产环境中不会包含堆栈跟踪。

🌐 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. The stack trace is not included in the production environment.

Note

将环境变量 NODE_ENV 设置为 production,以在生产模式下运行应用。

🌐 Set the environment variable NODE_ENV to production, to run the app in production mode.

当写入错误时,以下信息会被添加到响应中:

🌐 When an error is written, the following information is added to the response:

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

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

🌐 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.

所以当你添加自定义错误处理程序时,你必须在响应头已经发送给客户端时,将处理委托给默认的 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:

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

请注意,如果你在代码中多次调用带有错误的 next(),即使已有自定义错误处理中间件,默认错误处理程序也可能被触发。

🌐 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.

其他错误处理中间件可以在 Express 中间件 找到。

🌐 Other error handling middleware can be found at Express middleware.

编写错误处理程序

🌐 Writing error handlers

以与其他中间件函数相同的方式定义错误处理中间件函数,但错误处理函数有四个参数而不是三个:(err, req, res, next)。例如:

🌐 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:

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

你应当在其他 app.use() 和路由调用之后最后定义错误处理中间件;例如:

🌐 You define error-handling middleware last, after other app.use() and routes calls; for example:

index.cjs
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
});

中间件函数内部的响应可以采用任何格式,例如 HTML 错误页面、简单消息或 JSON 字符串。

🌐 Responses from within a middleware function can be in any format, such as an HTML error page, a simple message, or a JSON string.

出于组织(以及更高层次框架)目的,你可以定义几个错误处理的中间件函数,就像定义常规中间件函数一样。例如,要为使用 XHR 发出的请求以及那些没有使用的请求定义一个错误处理器:

🌐 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:

index.cjs
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);

在此示例中,通用的 logErrors 可能会将请求和错误信息写入 stderr,例如:

🌐 In this example, the generic logErrors might write request and error information to stderr, for example:

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

在这个例子中,clientErrorHandler 被定义如下;在这种情况下,错误会被明确地传递给下一个。

🌐 Also in this example, clientErrorHandler is defined as follows; in this case, the error is explicitly passed along to the next one.

请注意,当在错误处理函数中不调用“next”时,你需要负责编写(并结束)响应。否则,这些请求将会“挂起”,并且无法被垃圾回收。

🌐 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.

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

按照以下方式实现“全捕获” errorHandler 函数(例如):

🌐 Implement the “catch-all” errorHandler function as follows (for example):

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

如果你有一个包含多个回调函数的路由处理程序,你可以使用 route 参数跳到下一个路由处理程序。例如:

🌐 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:

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);
});
}
);

在这个例子中,getPaidContent 处理程序将被跳过,但 app 中针对 /a_route_behind_paywall 的任何剩余处理程序将继续执行。

🌐 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.

Note

next()next(err) 的调用表示当前处理程序已完成以及处于何种状态。 next(err) 将跳过链中所有剩余的处理程序,除了那些按上述方式设置来处理错误的处理程序。

🌐 Calls to next() and next(err) indicate that the current handler is complete and in what state. next(err) will skip all remaining handlers in the chain except for those that are set up to handle errors as described above.