主题
异步与错误处理
在开发现代的 Express 应用时,异步编程是必不可少的。尤其是涉及数据库操作时,Mongoose 提供了强大的异步支持,包括通过 async/await
和回调函数来处理数据库请求。本章节将介绍如何在 Express 和 Mongoose 中正确处理异步操作及错误,确保应用的高可用性和稳定性。
异步操作
在 Express 中,大多数操作(如请求处理、数据库交互等)都是异步的。异步操作常常涉及回调函数或 Promise
,而 async/await
是处理异步操作的现代方式。
1. 使用 async/await
处理异步操作
async/await
提供了一种更直观的方式来处理异步操作,可以让你像同步代码一样编写异步代码。以下是一个使用 async/await
处理数据库查询的示例:
js
// 使用 async/await 查询用户
async function getUser(req, res) {
try {
const user = await User.findOne({ username: req.params.username });
if (!user) {
return res.status(404).send('用户未找到');
}
res.json(user);
} catch (err) {
console.error('查询失败:', err);
res.status(500).send('服务器错误');
}
}
2. 在 Express 路由中使用 async/await
在 Express 路由中,我们可以使用 async/await
来简化代码并处理异步任务。以下是一个完整的示例:
js
app.get('/user/:username', async (req, res) => {
try {
const user = await User.findOne({ username: req.params.username });
if (!user) {
return res.status(404).send('用户未找到');
}
res.json(user);
} catch (err) {
console.error('数据库查询失败:', err);
res.status(500).send('服务器错误');
}
});
3. 使用 Promise
进行异步操作
Promise
是 JavaScript 中处理异步操作的基础,可以链式调用 then
和 catch
来处理结果和错误。虽然 async/await
更直观,但在某些场景下,Promise
依然有其优势。
js
User.findOne({ username: req.params.username })
.then((user) => {
if (!user) {
return res.status(404).send('用户未找到');
}
res.json(user);
})
.catch((err) => {
console.error('查询失败:', err);
res.status(500).send('服务器错误');
});
错误处理
在开发应用时,错误处理是一个重要的方面。良好的错误处理可以帮助我们捕获问题并确保应用稳定运行。
1. 使用 try/catch
捕获错误
在异步函数中,try/catch
用于捕获可能发生的异常。如果异步操作失败,错误会被捕获并处理。
js
async function updateUser(req, res) {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).send('用户未找到');
}
user.name = req.body.name;
await user.save();
res.json(user);
} catch (err) {
console.error('更新失败:', err);
res.status(500).send('服务器错误');
}
}
2. Express 中间件处理错误
Express 提供了一个错误处理中间件来集中处理所有路由中的错误。你可以定义一个全局的错误处理器来捕获未处理的错误。
js
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('服务器错误');
});
此中间件需要放在所有路由和其他中间件之后,才能捕获整个应用中的错误。
3. 自定义错误处理类
为了增强错误处理的可读性和一致性,我们可以创建一个自定义的错误处理类。该类可以将不同类型的错误(如验证错误、数据库错误等)封装成统一的格式。
js
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
module.exports = AppError;
使用自定义错误类的示例:
js
const AppError = require('./AppError');
async function getUser(req, res, next) {
try {
const user = await User.findOne({ username: req.params.username });
if (!user) {
return next(new AppError('用户未找到', 404));
}
res.json(user);
} catch (err) {
next(err); // 将错误传递给错误处理中间件
}
}
4. 错误响应格式化
为了保持一致的错误响应格式,可以将所有错误信息返回给客户端时格式化为统一结构。通常,错误信息会包含 status
、message
和其他相关的错误细节。
js
// 错误处理中间件
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
const status = err.status || 'error';
const message = err.message || '服务器错误';
res.status(statusCode).json({
status,
message,
});
});
5. 捕获未处理的异常
为了防止未处理的异常崩溃应用,通常可以在应用启动时添加 process.on
事件监听,捕获未处理的 promise rejection 和异常。
js
// 捕获未处理的 promise rejection
process.on('unhandledRejection', (err) => {
console.error('未处理的 promise rejection:', err);
// 这里可以选择关闭应用或进行其他处理
});
// 捕获未处理的异常
process.on('uncaughtException', (err) => {
console.error('未处理的异常:', err);
// 可以选择退出应用
});
异常日志记录
在生产环境中,捕获并记录错误日志是一个重要的环节。通过记录日志,我们可以更轻松地调试和排查错误。
1. 使用日志库
可以使用流行的日志库,如 winston
或 morgan
来记录应用中的错误信息和请求日志。
bash
npm install winston
配置 winston
日志记录器:
js
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.Console({ format: winston.format.simple() }),
new winston.transports.File({ filename: 'error.log', level: 'error' }),
],
});
module.exports = logger;
在错误处理中间件中使用 winston
记录错误:
js
const logger = require('./logger');
app.use((err, req, res, next) => {
logger.error(err.stack);
res.status(500).send('服务器错误');
});
总结
- 异步操作:在 Express 中,使用
async/await
处理异步任务能够让代码更简洁、易读,并且避免回调地狱。 - 错误处理:通过
try/catch
捕获异常,使用全局错误处理中间件来处理未捕获的错误。 - 自定义错误:可以使用自定义错误类来统一管理和处理错误。
- 日志记录:通过日志库记录错误信息,有助于排查和调试问题。
- 全局异常捕获:通过
process.on
捕获未处理的异常和 promise rejection,确保应用稳定运行。
通过良好的异步处理和错误管理,我们可以确保 Express 应用在生产环境中的可靠性和稳定性。