JavaScript错误处理

什么是错误?

在JavaScript中,错误是指程序执行过程中发生的异常情况,导致程序无法正常继续执行。错误可以是语法错误、运行时错误或逻辑错误。

错误类型

JavaScript内置了多种错误类型,它们都继承自Error对象:

1. SyntaxError(语法错误)

当JavaScript引擎遇到不符合语法规则的代码时抛出。

// 语法错误:缺少右括号
console.log('Hello World;

// 语法错误:使用了关键字作为变量名
const let = 10;

2. ReferenceError(引用错误)

当尝试访问一个不存在的变量或函数时抛出。

// 引用错误:未声明的变量
console.log(undefinedVariable);

// 引用错误:函数名拼写错误
fnctionName();

3. TypeError(类型错误)

当操作的类型不符合预期时抛出。

// 类型错误:对非函数类型调用函数
const num = 10;
num();

// 类型错误:访问null或undefined的属性
const obj = null;
console.log(obj.property);

4. RangeError(范围错误)

当数值超出有效范围时抛出。

// 范围错误:数组长度为负数
const arr = new Array(-1);

// 范围错误:递归过深
function infiniteRecursion() {
  return infiniteRecursion();
}

infiniteRecursion();

5. URIError(URI错误)

当使用全局URI处理函数时参数不正确时抛出。

// URI错误:无效的URI编码
decodeURIComponent('%');

// URI错误:无效的URI
encodeURIComponent('\uD800');

6. EvalError(eval错误)

eval()函数执行错误时抛出,ES5后很少使用。

try/catch语句

try/catch语句是JavaScript中处理异常的主要机制,它允许我们捕获和处理运行时错误,而不会导致整个程序崩溃。

基本语法

try {
  // 可能会抛出错误的代码
  const result = riskyOperation();
  console.log(result);
} catch (error) {
  // 处理错误的代码
  console.error('发生错误:', error.message);
} finally {
  // 无论是否发生错误,都会执行的代码
  console.log('操作完成');
}

示例:处理不同类型的错误

function processInput(input) {
  try {
    if (input === undefined) {
      throw new ReferenceError('输入未定义');
    }
    
    if (typeof input !== 'number') {
      throw new TypeError('输入必须是数字');
    }
    
    if (input < 0 || input > 100) {
      throw new RangeError('输入必须在0-100之间');
    }
    
    return input * 2;
  } catch (error) {
    console.error(`错误类型:${error.name}`);
    console.error(`错误消息:${error.message}`);
    console.error(`错误堆栈:${error.stack}`);
    return null;
  } finally {
    console.log('处理完成');
  }
}

// 测试
console.log(processInput(50)); // 100
console.log(processInput('abc')); // null(TypeError)
console.log(processInput(-10)); // null(RangeError)

抛出自定义错误

使用throw关键字可以抛出自定义错误,它可以是任何值,但通常是Error对象或其派生类的实例。

抛出基本错误

function divide(a, b) {
  if (b === 0) {
    throw '除数不能为零';
  }
  return a / b;
}

try {
  console.log(divide(10, 0));
} catch (error) {
  console.error('错误:', error); // 错误:除数不能为零
}

抛出Error对象

function divide(a, b) {
  if (b === 0) {
    throw new Error('除数不能为零');
  }
  return a / b;
}

try {
  console.log(divide(10, 0));
} catch (error) {
  console.error(`错误类型:${error.name}`); // 错误类型:Error
  console.error(`错误消息:${error.message}`); // 错误消息:除数不能为零
}

自定义错误类型

可以通过继承Error类来创建自定义错误类型,以便更好地分类和处理不同类型的错误。

// 自定义错误类
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
    this.timestamp = new Date();
  }
}

class DatabaseError extends Error {
  constructor(message, query) {
    super(message);
    this.name = 'DatabaseError';
    this.query = query;
    this.timestamp = new Date();
  }
}

// 使用自定义错误
function saveUser(user) {
  try {
    // 验证输入
    if (!user.name) {
      throw new ValidationError('用户名不能为空', 'name');
    }
    
    if (!user.email) {
      throw new ValidationError('邮箱不能为空', 'email');
    }
    
    // 模拟数据库操作
    const success = Math.random() > 0.5;
    if (!success) {
      throw new DatabaseError('保存用户失败', 'INSERT INTO users VALUES (...)');
    }
    
    console.log('用户保存成功');
    return true;
  } catch (error) {
    if (error instanceof ValidationError) {
      console.error(`验证错误 [${error.field}]:${error.message}`);
    } else if (error instanceof DatabaseError) {
      console.error(`数据库错误:${error.message}`);
      console.error(`SQL查询:${error.query}`);
    } else {
      console.error(`未知错误:${error.message}`);
    }
    return false;
  }
}

// 测试
saveUser({ name: '', email: 'test@example.com' });
saveUser({ name: 'John', email: 'john@example.com' });

错误处理策略

1. 局部错误处理

在可能发生错误的特定代码块周围使用try/catch

function parseJSON(jsonString) {
  try {
    return JSON.parse(jsonString);
  } catch (error) {
    console.error('JSON解析错误:', error.message);
    return null;
  }
}

2. 全局错误处理

使用window.onerrorprocess.on(&#39;uncaughtException&#39;)捕获未处理的全局错误。

// 浏览器环境
window.onerror = function(message, source, lineno, colno, error) {
  console.error('全局错误:', message);
  console.error('错误源:', source);
  console.error('行号:', lineno);
  console.error('列号:', colno);
  console.error('错误对象:', error);
  
  // 返回true阻止浏览器默认错误处理
  return true;
};

// Node.js环境
process.on('uncaughtException', (error) => {
  console.error('未捕获的异常:', error);
  // 通常需要重启进程
  process.exit(1);
});

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

3. Promise错误处理

使用.catch()方法处理Promise链中的错误。

fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP错误!状态:${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('请求错误:', error.message);
  });

4. async/await错误处理

使用try/catch处理async/await中的错误。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error(`HTTP错误!状态:${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('请求错误:', error.message);
    return null;
  }
}

错误处理的最佳实践

  1. 早抛出,晚捕获:在发现错误时立即抛出,在合适的层次捕获并处理
  2. 具体错误类型:使用具体的错误类型,便于调试和处理
  3. 有用的错误消息:提供清晰、具体的错误消息,包含足够的上下文信息
  4. 记录错误:将错误记录到日志系统,便于监控和调试
  5. 不忽略错误:不要捕获错误后不做任何处理
  6. 区分预期和非预期错误:预期错误(如用户输入错误)应友好提示,非预期错误应记录并上报
  7. 使用自定义错误:创建自定义错误类型,提高错误处理的针对性
  8. 保持错误处理简洁:不要在错误处理代码中添加复杂逻辑
  9. 测试错误情况:编写测试用例覆盖各种错误场景
  10. 考虑恢复策略:对于某些错误,考虑实现恢复机制,而不是直接终止程序

调试技巧

1. 使用console.error()和console.trace()

try {
  // 可能出错的代码
} catch (error) {
  console.error('错误详情:', error);
  console.trace('错误堆栈:');
}

2. 断点调试

使用浏览器开发者工具或Node.js调试器设置断点,逐行执行代码并观察变量值。

3. 错误边界(React)

在React应用中,使用错误边界组件捕获子组件树中的JavaScript错误。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('React错误:', error);
    console.error('错误信息:', errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>出现错误,请刷新页面重试。</h1>;
    }

    return this.props.children;
  }
}

错误监控与上报

对于生产环境,建议使用错误监控服务来收集和分析错误:

  • Sentry:全面的错误监控和追踪服务
  • Bugsnag:专注于移动应用和Web应用的错误监控
  • New Relic:应用性能监控和错误追踪
  • Datadog:APM、日志和错误监控

总结

JavaScript错误处理是编写健壮应用的重要组成部分。通过理解不同类型的错误,掌握try/catch语句的使用,创建自定义错误类型,以及实施有效的错误处理策略,可以提高应用的可靠性和可维护性。

良好的错误处理应该:

  • 提供有用的错误信息
  • 不泄露敏感信息
  • 允许应用优雅地恢复
  • 便于调试和监控
  • 提高用户体验

在实际开发中,应根据具体场景选择合适的错误处理方式,并遵循最佳实践,以确保应用在各种情况下都能稳定运行。

练习

  1. 创建一个函数,接收一个字符串参数,尝试将其转换为数字,如果转换失败,抛出自定义错误
  2. 实现一个自定义错误类NetworkError,包含错误码、错误消息和时间戳
  3. 使用try/catchfinally语句实现文件读写操作的错误处理(模拟)
  4. 编写一个异步函数,使用async/await处理Promise错误
  5. 实现一个简单的错误监控函数,记录错误信息到控制台
  6. 对比不同错误处理方式(回调、Promise、async/await)的优缺点
« 上一篇 JavaScript异步编程 下一篇 » JavaScript DOM操作