Skip to content
On this page

Express错误处理

错误处理是任何Web应用的重要组成部分。Express提供了内置的错误处理机制,同时允许开发者自定义错误处理逻辑。

Express错误处理概览

Express错误处理遵循以下原则:

  1. 错误处理中间件有四个参数:(err, req, res, next)
  2. 错误处理中间件应定义在其他app.use()和路由调用之后
  3. Express会捕获路由和中间件执行过程中的同步和异步错误

错误处理中间件

基本错误处理中间件

javascript
// 错误处理中间件必须有四个参数
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

错误处理中间件的位置

javascript
const express = require('express');
const app = express();

// 普通中间件
app.use((req, res, next) => {
  console.log(`${req.method} ${req.path}`);
  next();
});

// 路由
app.get('/', (req, res) => {
  res.send('Hello World');
});

// 错误处理中间件 - 必须在所有路由定义之后
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

错误类型和处理

同步错误

javascript
app.get('/sync-error', (req, res) => {
  // 同步错误会被自动捕获
  throw new Error('This is a synchronous error');
});

异步错误

javascript
app.get('/async-error', (req, res, next) => {
  // 异步错误需要显式调用next()
  setTimeout(() => {
    try {
      throw new Error('This is an asynchronous error');
    } catch (err) {
      next(err); // 将错误传递给错误处理中间件
    }
  }, 100);
});

// 或者使用Promise
app.get('/promise-error', (req, res, next) => {
  someAsyncFunction()
    .then(result => {
      res.json(result);
    })
    .catch(err => {
      next(err); // 传递错误
    });
});

异步函数错误处理

javascript
// 使用async/await的错误处理
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

app.get('/async-await-error', asyncHandler(async (req, res) => {
  const data = await someAsyncOperation();
  res.json(data);
}));

// 或者在函数内部处理错误
app.get('/async-await-error-2', async (req, res, next) => {
  try {
    const data = await someAsyncOperation();
    res.json(data);
  } catch (err) {
    next(err);
  }
});

自定义错误类

创建自定义错误

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

// 使用自定义错误
app.get('/custom-error', (req, res, next) => {
  const error = new AppError('Resource not found', 404);
  next(error);
});

不同类型的错误

javascript
class ValidationError extends AppError {
  constructor(message) {
    super(message, 400);
    this.type = 'validation_error';
  }
}

class AuthenticationError extends AppError {
  constructor(message) {
    super(message, 401);
    this.type = 'authentication_error';
  }
}

class AuthorizationError extends AppError {
  constructor(message) {
    super(message, 403);
    this.type = 'authorization_error';
  }
}

// 在路由中使用
app.post('/login', (req, res, next) => {
  if (!req.body.username || !req.body.password) {
    return next(new ValidationError('Username and password are required'));
  }
  
  // 其他逻辑...
});

全局错误处理

开发环境错误处理

javascript
const sendErrorDev = (err, res) => {
  res.status(err.statusCode).json({
    status: err.status,
    error: err,
    message: err.message,
    stack: err.stack
  });
};

生产环境错误处理

javascript
const sendErrorProd = (err, res) => {
  // 可操作错误:发送可理解的错误信息
  if (err.isOperational) {
    res.status(err.statusCode).json({
      status: err.status,
      message: err.message
    });
  } else {
    // 编程或未知错误:发送通用错误信息
    console.error('ERROR 💥', err);
    res.status(500).json({
      status: 'error',
      message: 'Something went very wrong!'
    });
  }
};

// 主错误处理中间件
app.use((err, req, res, next) => {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';

  if (process.env.NODE_ENV === 'development') {
    sendErrorDev(err, res);
  } else {
    sendErrorProd(err, res);
  }
});

特定错误处理

404错误处理

javascript
// 404错误处理 - 应放在所有路由之后
app.all('*', (req, res, next) => {
  const err = new AppError(`Can't find ${req.originalUrl} on this server!`, 404);
  next(err);
});

数据库错误处理

javascript
const handleDBError = (err) => {
  if (err.name === 'CastError') {
    const message = `Invalid ${err.path}: ${err.value}.`;
    return new AppError(message, 400);
  }
  
  if (err.code === 11000) {
    const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0];
    const message = `Duplicate field value: ${value}. Please use another value!`;
    return new AppError(message, 400);
  }
  
  if (err.name === 'ValidationError') {
    const errors = Object.values(err.errors).map(el => el.message);
    const message = `Invalid input data. ${errors.join('. ')}`;
    return new AppError(message, 400);
  }
  
  return err;
};

// 在错误处理中间件中使用
app.use((err, req, res, next) => {
  let error = { ...err };
  error.message = err.message;

  // 处理MongoDB错误
  if (err.name === 'CastError') error = handleDBError(error);
  
  res.status(error.statusCode || 500).json({
    status: error.status || 'error',
    message: error.message || 'Internal Server Error'
  });
});

错误记录和监控

使用日志记录错误

javascript
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'express-app' },
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

// 在错误处理中间件中记录错误
app.use((err, req, res, next) => {
  logger.error({
    message: err.message,
    stack: err.stack,
    url: req.url,
    method: req.method,
    ip: req.ip,
    userAgent: req.get('User-Agent')
  });

  res.status(err.statusCode || 500).json({
    status: 'error',
    message: err.message
  });
});

未捕获异常和拒绝的Promise

全局错误处理

javascript
// 处理未捕获的异常
process.on('uncaughtException', (err) => {
  console.log('UNCAUGHT EXCEPTION! 💥 Shutting down...');
  console.log(err.name, err.message);
  
  // 正常关闭服务器
  process.exit(1);
});

// 处理未处理的Promise拒绝
process.on('unhandledRejection', (err) => {
  console.log('UNHANDLED REJECTION! 💥 Shutting down...');
  console.log(err.name, err.message);
  
  // 正常关闭服务器
  server.close(() => {
    process.exit(1);
  });
});

验证错误处理

使用Joi进行验证和错误处理

javascript
const Joi = require('joi');

const userSchema = Joi.object({
  name: Joi.string().min(3).max(30).required(),
  email: Joi.string().email().required(),
  age: Joi.number().integer().min(0).max(120)
});

app.post('/users', (req, res, next) => {
  const { error, value } = userSchema.validate(req.body);
  
  if (error) {
    const err = new AppError(`Validation Error: ${error.details[0].message}`, 400);
    return next(err);
  }
  
  // 使用验证后的数据
  req.validatedData = value;
  next();
});

中间件错误处理

路由中间件错误

javascript
// 认证中间件错误处理
const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return next(new AuthenticationError('Access token required'));
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    if (err.name === 'JsonWebTokenError') {
      return next(new AuthenticationError('Invalid token'));
    }
    if (err.name === 'TokenExpiredError') {
      return next(new AuthenticationError('Token expired'));
    }
    return next(err);
  }
};

app.get('/profile', authenticate, (req, res) => {
  res.json(req.user);
});

错误处理最佳实践

1. 统一错误响应格式

javascript
const errorResponse = (res, statusCode, message, errors = null) => {
  const response = {
    status: 'error',
    message: message,
    timestamp: new Date().toISOString(),
    path: res.req.originalUrl
  };
  
  if (errors) {
    response.errors = errors;
  }
  
  return res.status(statusCode).json(response);
};

// 使用统一错误响应
app.use((err, req, res, next) => {
  errorResponse(res, err.statusCode || 500, err.message);
});

2. 环境特定错误处理

javascript
const errorController = {
  sendErrorDev: (err, res) => {
    res.status(err.statusCode).json({
      status: err.status,
      error: err,
      message: err.message,
      stack: err.stack
    });
  },
  
  sendErrorProd: (err, res) => {
    if (err.isOperational) {
      res.status(err.statusCode).json({
        status: err.status,
        message: err.message
      });
    } else {
      console.error('ERROR 💥', err);
      res.status(500).json({
        status: 'error',
        message: 'Something went wrong!'
      });
    }
  }
};

app.use((err, req, res, next) => {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';

  if (process.env.NODE_ENV === 'development') {
    errorController.sendErrorDev(err, res);
  } else {
    errorController.sendErrorProd(err, res);
  }
});

3. 错误分类处理

javascript
// 根据错误类型进行不同处理
const handleSpecificErrors = (err) => {
  // 数据库连接错误
  if (err.code === 'ECONNREFUSED') {
    return new AppError('Database connection failed', 500);
  }
  
  // 超时错误
  if (err.code === 'ETIMEDOUT') {
    return new AppError('Request timeout', 408);
  }
  
  // 资源未找到
  if (err.status === 404) {
    return new AppError('Resource not found', 404);
  }
  
  return err;
};

通过正确的错误处理,可以提高应用的稳定性和用户体验,同时便于调试和监控应用的运行状态。