Express 错误处理
在使用 Express 开发 Web 应用时,错误处理也非常重要。Express 提供了一套机制来捕获和处理在路由处理程序和中间件中发生的错误,无论是同步还是异步代码。
捕获错误
同步代码中的错误
当同步代码在执行过程中抛出错误时,Express 会自动捕获这些错误。例如:
app.get('/', (req, res) => {
throw new Error('同步错误'); // Express 会自动捕获这个错误
});
异步代码中的错误
对于异步函数中的错误,你需要显式地将错误传递给 next()
函数,以便 Express 能够捕获并处理它。这通常是在回调函数或 Promise 的 .catch()
方法中完成的。
例如,使用 Node.js 的 fs
模块读取文件时:
app.get('/', (req, res, next) => {
fs.readFile('/file-does-not-exist', (err, data) => {
if (err) {
next(err); // 将错误传递给 Express
} else {
res.send(data);
}
});
});
Promise 中的错误
从 Express 5 开始,如果路由处理程序或中间件返回一个 Promise,并且这个 Promise 被拒绝,Express 会自动调用 next(value)
,其中 value
是 Promise 被拒绝时传递的原因(通常是一个错误对象)。
例如,使用 async/await 语法:
app.get('/user/:id', async (req, res, next) => {
try {
const user = await getUserById(req.params.id);
res.send(user);
} catch (err) {
next(err); // 如果 getUserById 抛出错误,这里会捕获并传递给 Express
}
});
其他异步错误
对于其他异步代码中的错误,如果你不能使用 async/await 语法,仍然可以使用 try...catch
块来捕获错误,并将其传递给 next()
函数。
例如,使用 setTimeout
:
app.get('/', (req, res, next) => {
setTimeout(() => {
try {
// 模拟异步错误
throw new Error('异步错误');
} catch (err) {
next(err); // 捕获错误并传递给 Express
}
}, 100);
});
使用 Promise 简化错误处理
使用 Promise 可以简化异步代码中的错误处理流程,特别是与返回 Promise 的函数交互时,可以直接在 Promise 链的末尾添加一个 .catch()
处理程序,并将 next
作为参数传递。
例如:
app.get('/', (req, res, next) => {
Promise.resolve() // 模拟一个立即解析的 Promise
.then(() => {
// 模拟异步错误
throw new Error('Promise 链中的错误');
})
.catch(next); // 错误会被传递给 Express
});
注意:在编写错误处理中间件时,通常不需要(也不应该)调用 next()
函数,因为错误处理中间件的任务就是处理错误并发送响应。如果在错误处理中间件中调用了 next()
,这可能会导致未定义的行为或额外的处理逻辑被执行。
编写自定义错误处理程序
自定义错误处理程序确实与其他中间件函数类似,但特别之处在于它们接收四个参数:(err, req, res, next)
。当 Express 遇到错误时,它会查找能够处理错误的中间件,即那些接受四个参数的中间件。
例如:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
在错误处理中间件中,通常不会调用 next()
,因为错误已经到达该中间件,意味着没有其他中间件能够或应该处理这个错误。调用 next(err)
在错误处理中间件中没有意义,因为 err
参数已经存在。
定义多个错误处理程序
可以根据需要定义多个错误处理程序,以处理不同类型或来源的错误。例如,你可能想要为 AJAX 请求(通常通过 XHR 或 Fetch API 发起)和非 AJAX 请求定义不同的错误处理程序。
// 假设这些函数已经在其他地方定义
function logErrors(err, req, res, next) {
console.error(err.stack);
next(err); // 传递错误到下一个错误处理程序
}
function clientErrorHandler(err, req, res, next) {
if (req.xhr) { // 检查请求是否是 AJAX 请求
res.status(500).json({ error: 'Something failed!' });
} else {
next(err); // 如果不是 AJAX 请求,将错误传递给下一个错误处理程序
}
}
function errorHandler(err, req, res) { // 注意这里通常不传递 next 参数
res.status(500);
res.render('error', { error: err }); // 假设你有一个名为 'error' 的视图模板
}
// 应用错误处理程序中间件
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);
注意
- 在错误处理中间件中,如果不调用
next()
,则需要确保你已经发送了响应(如上面的例子所示)。否则,客户端将永远等待响应,导致请求挂起。 - 在错误处理中间件链的最后一个中间件中,通常不接收
next
参数,因为你已经知道没有更多的错误处理程序可以调用。 - 错误处理中间件应该始终位于其他中间件和路由定义之后,以确保它们能够捕获到所有未被处理的错误。