什么是自定义错误
在JavaScript开发中,内置的错误类型(如 Error、TypeError、RangeError 等)已经覆盖了大部分常见场景。但当我们构建大型应用或库时,往往需要更具体的错误信息来区分不同异常情况。通过自定义错误,我们可以创建带有特殊属性或方法的错误实例,使错误处理更加精确和优雅。
基础:使用 throw 抛出一个字符串或对象
在深入类之前,先了解最简单的抛出方式。你可以抛出任何类型的值,但通常抛出一个 Error 实例或继承自 Error 的对象是最佳实践,因为它提供了 stack 和 message 等标准化属性。
// 基础抛出字符串(不推荐,因为缺少堆栈信息)
throw '发生了错误';
// 抛出普通对象(不够规范)
throw { code: 404, message: '资源未找到' };抛出自定义错误的核心:继承 Error 类
JavaScript 允许我们通过 class extends Error 创建自定义错误类。这样创建的错误实例就拥有了 name、message 和 stack 属性,并且可以被 instanceof 检测。
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError'; // 显式设置错误名称
this.field = field; // 自定义属性,用于标识哪个字段验证失败
}
}实战:定义多个自定义错误类型
假设我们正在开发一个用户注册系统,需要区分不同类型的验证失败。可以创建几个具体的错误子类:
class EmailError extends Error {
constructor(message, email) {
super(message);
this.name = 'EmailError';
this.email = email;
}
}
class PasswordError extends Error {
constructor(message, password) {
super(message);
this.name = 'PasswordError';
this.password = password;
}
}在函数中抛出自定义错误
使用 throw new CustomError(...) 即可抛出自定义错误实例。注意 throw 会中断当前函数的执行,将控制权转移到最近的 catch 块。
function validateUser(email, password) {
if (!email.includes('@')) {
throw new EmailError('邮箱格式不正确', email);
}
if (password.length < 8) {
throw new PasswordError('密码长度至少8位', password);
}
// 其他验证...
return { email, password };
}
// 使用示例
try {
validateUser('testippipp.com', '123');
} catch (error) {
if (error instanceof EmailError) {
console.error('邮箱错误:', error.message, error.email);
} else if (error instanceof PasswordError) {
console.error('密码错误:', error.message, error.password);
} else {
console.error('未知错误:', error);
}
}上述代码中,instanceof 检测可以精确匹配自定义错误类型,从而实现差异化的错误处理逻辑。
使用 Error.captureStackTrace 优化堆栈(Node.js/现代浏览器)
在构造函数中调用 Error.captureStackTrace(this, this.constructor) 可以避免自定义构造函数本身出现在堆栈中,使调试信息更干净。这对于库开发者尤其有用。
class DatabaseError extends Error {
constructor(message, query) {
super(message);
this.name = 'DatabaseError';
this.query = query;
// 捕获堆栈但不包括当前构造函数
Error.captureStackTrace(this, this.constructor);
}
}
throw new DatabaseError('查询超时', 'SELECT * FROM users');结合 try...catch...finally 处理常见场景
自定义错误最常见的用途是在 API 调用或数据处理中,将底层错误包装为更友好的上层错误。以下是一个实际例子:
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
async function fetchUserData(userId) {
try {
const response = await fetch(`https://api.ippipp.com/users/${userId}`);
if (!response.ok) {
throw new NetworkError('请求用户数据失败', response.status);
}
return await response.json();
} catch (error) {
// 重新抛出或包装
if (error instanceof NetworkError) {
throw error; // 保持原来的错误类型
}
throw new Error('获取数据时发生未知错误');
}
}注意事项与最佳实践
- 始终继承
Error:不要直接继承Object,否则丢失 stack 等关键信息。 - 设置
name属性:在构造函数中显式设置this.name,便于识别错误类型。 - 避免抛出非 Error 对象:尽量抛出
Error或其子类实例,以确保下游可以统一处理message和stack。 - 使用
instanceof判断:不要依赖error.message字符串比较,那样脆弱且不可靠。 - 考虑序列化:如果自定义错误需要被序列化(如发回前端),请确保添加
toJSON方法。
一个完整的示例:自定义错误与错误处理函数
下面的代码展示了如何定义一套自定义错误体系,并编写一个全局的错误处理函数来统一显示错误信息。
// 自定义错误基类
class AppError extends Error {
constructor(message, code) {
super(message);
this.name = 'AppError';
this.code = code; // 自定义错误码
}
}
// 具体错误类型
class UserNotFoundError extends AppError {
constructor(userId) {
super(`用户 ${userId} 未找到`, 'USER_NOT_FOUND');
this.name = 'UserNotFoundError';
this.userId = userId;
}
}
class DuplicateEntryError extends AppError {
constructor(field, value) {
super(`字段 ${field} 的值 '${value}' 已存在`, 'DUPLICATE_ENTRY');
this.name = 'DuplicateEntryError';
this.field = field;
this.value = value;
}
}
// 全局错误处理函数
function handleError(error) {
if (error instanceof UserNotFoundError) {
console.warn(`[${error.code}] ${error.message} (用户ID: ${error.userId})`);
} else if (error instanceof DuplicateEntryError) {
console.warn(`[${error.code}] ${error.message} (字段: ${error.field})`);
} else {
console.error('未处理错误:', error);
}
}
// 测试
try {
throw new UserNotFoundError(12345);
} catch (e) {
handleError(e);
}
try {
throw new DuplicateEntryError('email', 'test@ippipp.com');
} catch (e) {
handleError(e);
}小结
通过自定义错误,我们可以让错误携带更多语义信息,提升代码的可读性和可维护性。核心步骤只有三步:
- 创建一个继承
Error的类。 - 在构造函数中设置
name和自定义属性。 - 使用
throw new YourCustomError(...)抛出实例,并在catch中通过instanceof进行精确处理。
掌握这些技巧后,你的 JavaScript 错误处理将变得更加专业和可靠。