翻译此页面

错误处理

错误处理是指 Express 如何捕获并处理同步代码与异步代码中发生的错误。 Express 自带默认错误处理器,因此你在开发初期无需自行编写。

错误捕获

确保 Express 能够捕获路由处理器和中间件运行时发生的所有错误,这一点至关重要。

Errors in synchronous code

路由处理函数与中间件内同步代码产生的错误无需额外处理。倘若同步代码抛出错误,Express 会捕获并处理该错误。举个例子:

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

Errors in asynchronous code

The recommended way to write asynchronous handlers is with async functions. Route handlers and middleware that return a Promise call next(value) automatically when they reject or throw an error, and async functions always return a Promise, so their errors reach Express with no extra work. 举个例子:

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

如果 getUserById 抛出错误或被拒绝(reject),next 将会使用抛出的错误或被拒绝的值来调用。如果未提供拒绝值,则会使用 Express 路由内置的默认错误对象调用 next。

如果向 next() 传入任意参数(字符串 'route' 除外),Express 会将当前请求视作出错,并跳过后续所有非错误处理的路由与中间件函数。

Working with promise chains

If you build a promise chain instead of using an async function, return the promise from the handler and Express will likewise call next automatically when it rejects:

app.get('/', (req, res) => {
return Promise.resolve().then(() => {
throw new Error('BROKEN'); // Express will catch this and call next.
});
});

If the promise is not returned, Express does not know it exists, and you must route the error yourself by providing next as the final catch handler. Without it, the rejection would be unhandled and crash the process:

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

This works because if a callback in the promise chain throws, the chain turns that exception into a rejection, which travels down to the final .catch. There, .catch calls its handler with the error as the first argument, which is exactly the argument next expects, so the error reaches Express.

Working with callback APIs

Errors produced by callback-based APIs, such as those in node:fs, are not thrown and are not part of any promise. The callback receives them as its first argument, and you must pass them to the next() function yourself, where Express will catch and process them. 举个例子:

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

如果序列中的回调函数不返回数据、仅返回错误,你可以将代码简化如下:

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

在上述示例中,next 被作为 fs.writeFile 的回调函数传入,无论是否存在错误都会被调用。若无错误,则执行第二个处理函数;否则 Express 会捕获并处理该错误。

你也可以使用处理程序链,将异步代码简化为简单逻辑,从而依靠同步错误捕获机制。举个例子:

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 contains a couple of trivial statements following the readFile call. 如果 readFile 引发错误,则会将错误传递给 Express;否则你将迅速回到处理程序链中下一个处理函数的同步错误处理流程。然后,上述示例会尝试处理数据。如果此过程失败,同步错误处理程序将会捕获该错误。如果你将此处理逻辑写在 readFile 回调函数内部,应用程序可能会直接退出,Express 错误处理程序将无法执行。

Finally, for asynchronous code that provides no error-first callback, such as a timer, catch errors inside the asynchronous code itself and pass them to Express:

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

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

无论你使用哪种方法,若希望 Express 错误处理程序被调用且应用程序持续运行,必须确保 Express 能够接收到错误。

默认错误处理程序

Express 内置了错误处理程序,可处理应用中可能出现的所有错误。这个默认的错误处理中间件函数会被添加到中间件函数栈的末尾。

如果你将错误传递给 next(),但未在自定义错误处理程序中处理它,该错误将由内置错误处理程序处理;错误信息和堆栈追踪信息会被返回给客户端。生产环境中不会包含堆栈追踪信息。

Note

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

当错误信息被输出时,响应中会附加以下信息:

  • res.statusCode 会从 err.status(或 err.statusCode)中获取设置。如果该值不在 4xx 或 5xx 范围内,则会被设置为 500。
  • res.statusMessage 会根据状态码自动设置。
  • 生产环境中,响应主体会展示状态码信息的 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(),仍可能触发默认错误处理程序。

Other error handling middleware can be found at Express middleware.

编写错误处理程序

错误处理中间件函数的定义方式与其他中间件函数基本相同,区别在于错误处理函数包含四个参数而非三个: (err, req, res, next)。举个例子:

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

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

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 字符串。

出于组织结构(以及高级框架)的目的,你可以定义多个错误处理中间件函数,这与你使用常规中间件函数的方式非常相似。例如,为使用 XHR 发起的请求和未使用 XHR 发起的请求分别定义错误处理程序:

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

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

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

Notice that when you do not call 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);
}
}

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

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. 举个例子:

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.

Note

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.