教程 express tutorial

异步与错误处理

在开发现代的 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 应用在生产环境中的可靠性和稳定性。