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应用的重要技术:
-
核心概念:
- 通过继承
Error类创建自定义错误 - 可以添加额外属性和方法
- 支持
instanceof检查
- 通过继承
-
高级技巧:
- 构建错误层次结构
- 添加错误代码标准化
- 错误包装与转换
- 异步错误处理
-
最佳实践:
- 区分操作错误(可恢复)和程序错误(不可恢复)
- 提供足够的错误上下文
- 保持错误类型语义明确
- 在适当层级处理错误
-
应用场景:
- API/服务层错误处理
- 前端错误边界
- 系统级错误监控
- 领域特定错误类型
通过合理设计和实现自定义错误类型,可以显著提高代码的可维护性和错误处理能力,为应用程序提供更好的可靠性和调试体验。
#前端开发
分享于 2025-03-25