14.1 自定义错误类型(继承 Error)

JavaScript 错误处理基础

JavaScript 提供了内置的 Error 类型作为所有错误的基础类。当我们需要创建特定领域的错误类型时,可以通过继承 Error 来创建自定义错误类。

内置 Error 类型回顾

try {
  throw new Error('Something went wrong');
} catch (error) {
  console.log(error.name);     // "Error"
  console.log(error.message);  // "Something went wrong"
  console.log(error.stack);    // 堆栈跟踪信息
}

创建自定义错误类

ES5 风格实现

function ValidationError(message) {
  this.name = 'ValidationError';
  this.message = message || 'Invalid input';
  this.stack = (new Error()).stack;
}

ValidationError.prototype = Object.create(Error.prototype);
ValidationError.prototype.constructor = ValidationError;

// 使用示例
try {
  throw new ValidationError('Email format is invalid');
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Validation failed:', error.message);
  } else {
    console.error('Unexpected error:', error);
  }
}

ES6+ 类继承实现

class ValidationError extends Error {
  constructor(message) {
    super(message); // 调用父类构造函数
    this.name = 'ValidationError';
    // 保持正确的堆栈跟踪(V8引擎)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, ValidationError);
    }
  }
}

// 使用示例
try {
  throw new ValidationError('Password too short');
} catch (error) {
  console.log(error instanceof Error);        // true
  console.log(error instanceof ValidationError); // true
  console.log(error.name);                   // "ValidationError"
  console.log(error.message);                // "Password too short"
  console.log(error.stack);                  // 堆栈跟踪
}

自定义错误的高级用法

添加额外属性

class DatabaseError extends Error {
  constructor(message, query, parameters) {
    super(message);
    this.name = 'DatabaseError';
    this.query = query;
    this.parameters = parameters;
    this.timestamp = new Date().toISOString();
  }
  
  toString() {
    return `${this.name}: ${this.message} (query: ${this.query})`;
  }
}

// 使用示例
try {
  throw new DatabaseError(
    'Connection timeout',
    'SELECT * FROM users WHERE id = ?',
    [123]
  );
} catch (error) {
  if (error instanceof DatabaseError) {
    console.error('Database operation failed:', {
      message: error.message,
      query: error.query,
      params: error.parameters,
      time: error.timestamp
    });
  }
}

错误层次结构

// 基础API错误
class ApiError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.name = 'ApiError';
    this.statusCode = statusCode || 500;
  }
}

// 特定API错误
class NotFoundError extends ApiError {
  constructor(resource) {
    super(`${resource} not found`, 404);
    this.name = 'NotFoundError';
    this.resource = resource;
  }
}

class UnauthorizedError extends ApiError {
  constructor(message = 'Authentication required') {
    super(message, 401);
    this.name = 'UnauthorizedError';
  }
}

// 使用示例
function fetchUser(userId) {
  if (!userId) throw new ValidationError('User ID is required');
  if (userId === 'admin') throw new UnauthorizedError();
  if (userId === '999') throw new NotFoundError('User');
  
  // 正常返回
  return { id: userId, name: 'Example User' };
}

try {
  const user = fetchUser('999');
} catch (error) {
  if (error instanceof ApiError) {
    console.error(`API Error [${error.statusCode}]: ${error.message}`);
    if (error instanceof NotFoundError) {
      console.error(`Resource not found: ${error.resource}`);
    }
  } else {
    console.error('Other error:', error);
  }
}

错误处理最佳实践

1. 错误分类与层次结构

// 应用错误层次结构示例
class AppError extends Error {}
class NetworkError extends AppError {}
class DatabaseError extends AppError {}
class ValidationError extends AppError {}

// 更具体的错误
class ConnectionTimeoutError extends NetworkError {
  constructor(timeout) {
    super(`Connection timed out after ${timeout}ms`);
    this.timeout = timeout;
  }
}

class UniqueConstraintError extends DatabaseError {
  constructor(field) {
    super(`${field} must be unique`);
    this.field = field;
  }
}

2. 错误代码标准化

class CodedError extends Error {
  constructor(message, code) {
    super(message);
    this.code = code;
  }
  
  toJSON() {
    return {
      code: this.code,
      message: this.message,
      stack: process.env.NODE_ENV === 'development' ? this.stack : undefined
    };
  }
}

// 使用预定义错误代码
const ERROR_CODES = {
  INVALID_INPUT: 'E1001',
  NOT_FOUND: 'E1002',
  UNAUTHORIZED: 'E1003'
};

class BusinessError extends CodedError {
  constructor(message, code = 'E0000') {
    super(message, code);
  }
}

// 使用示例
throw new BusinessError('Invalid email format', ERROR_CODES.INVALID_INPUT);

3. 异步错误处理

class AsyncError extends Error {
  constructor(message) {
    super(message);
    this.name = 'AsyncError';
  }
}

async function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new AsyncError('Failed to fetch data'));
    }, 1000);
  });
}

// 使用async/await处理
(async () => {
  try {
    await fetchData();
  } catch (error) {
    if (error instanceof AsyncError) {
      console.error('Async operation failed:', error.message);
    }
  }
})();

// 使用Promise.catch处理
fetchData().catch(error => {
  console.error('Caught error:', error.name, error.message);
});

4. 错误转换与包装

class ErrorWrapper {
  static wrap(error, context) {
    if (error instanceof CustomError) {
      return error;
    }
    
    const wrappedError = new CustomError(context.message || error.message);
    wrappedError.originalError = error;
    wrappedError.context = context;
    
    return wrappedError;
  }
}

class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = 'CustomError';
  }
}

// 使用示例
try {
  JSON.parse('invalid json');
} catch (error) {
  throw ErrorWrapper.wrap(error, {
    message: 'Failed to parse JSON',
    file: 'config.json'
  });
}

实际应用场景

1. Express 错误处理中间件

class HttpError extends Error {
  constructor(statusCode, message) {
    super(message);
    this.statusCode = statusCode;
  }
}

// 中间件
function errorHandler(err, req, res, next) {
  if (err instanceof HttpError) {
    res.status(err.statusCode).json({
      error: {
        message: err.message,
        type: err.name
      }
    });
  } else {
    // 未知错误
    res.status(500).json({
      error: {
        message: 'Internal Server Error',
        type: 'UnexpectedError'
      }
    });
    
    // 记录完整错误
    console.error('Unexpected error:', err);
  }
}

// 路由中使用
app.get('/users/:id', (req, res, next) => {
  const user = getUserById(req.params.id);
  if (!user) {
    return next(new HttpError(404, 'User not found'));
  }
  res.json(user);
});

app.use(errorHandler);

2. React 组件错误边界

class ComponentError extends Error {
  constructor(message, componentStack) {
    super(message);
    this.name = 'ComponentError';
    this.componentStack = componentStack;
  }
}

class ErrorBoundary extends React.Component {
  state = { error: null };
  
  static getDerivedStateFromError(error) {
    let componentError = error;
    if (!(error instanceof ComponentError)) {
      componentError = new ComponentError(
        error.message,
        error.stack
      );
      componentError.originalError = error;
    }
    return { error: componentError };
  }
  
  componentDidCatch(error, info) {
    // 可以在这里记录错误
    console.error('Component Error:', error, info.componentStack);
  }
  
  render() {
    if (this.state.error) {
      return (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>{this.state.error.message}</p>
          {this.props.showDetails && (
            <pre>{this.state.error.componentStack}</pre>
          )}
        </div>
      );
    }
    return this.props.children;
  }
}

// 使用示例
function BuggyComponent() {
  throw new Error('Test error');
}

function App() {
  return (
    <ErrorBoundary showDetails={true}>
      <BuggyComponent />
    </ErrorBoundary>
  );
}

3. Node.js 应用错误处理

class AppError extends Error {
  constructor(message, options = {}) {
    super(message);
    this.name = this.constructor.name;
    this.isOperational = options.isOperational || true;
    this.code = options.code || 'INTERNAL_ERROR';
    this.metadata = options.metadata || {};
    
    Error.captureStackTrace(this, this.constructor);
  }
}

// 进程未捕获异常处理
process.on('uncaughtException', error => {
  if (error instanceof AppError && error.isOperational) {
    logger.warn('Operational error:', error);
  } else {
    logger.error('Critical error:', error);
    // 可能需要重启进程
    process.exit(1);
  }
});

// 未处理的Promise拒绝
process.on('unhandledRejection', (reason, promise) => {
  logger.error('Unhandled rejection at:', promise, 'reason:', reason);
});

// 使用示例
function criticalOperation() {
  throw new AppError('Database connection failed', {
    isOperational: false,
    code: 'DB_CONNECTION_FAILED',
    metadata: { host: 'db.example.com', port: 5432 }
  });
}

try {
  criticalOperation();
} catch (error) {
  if (!error.isOperational) {
    process.emit('uncaughtException', error);
  }
}

总结

自定义错误类型是构建健壮JavaScript应用的重要技术:

  1. 核心概念

    • 通过继承 Error 类创建自定义错误
    • 可以添加额外属性和方法
    • 支持 instanceof 检查
  2. 高级技巧

    • 构建错误层次结构
    • 添加错误代码标准化
    • 错误包装与转换
    • 异步错误处理
  3. 最佳实践

    • 区分操作错误(可恢复)和程序错误(不可恢复)
    • 提供足够的错误上下文
    • 保持错误类型语义明确
    • 在适当层级处理错误
  4. 应用场景

    • API/服务层错误处理
    • 前端错误边界
    • 系统级错误监控
    • 领域特定错误类型

通过合理设计和实现自定义错误类型,可以显著提高代码的可维护性和错误处理能力,为应用程序提供更好的可靠性和调试体验。

#前端开发 分享于 2025-03-25

【 内容由 AI 共享,不代表本站观点,请谨慎参考 】