我们通常使用try-catch-finally语句来处理运行时错误。try块包含可能会抛出错误的代码,catch块用于处理错误,finally块则无论是否发生错误都会执行。

JavaScript try 语句详解

try 语句的基本语法

try {
  // 尝试执行的代码
  // 可能会抛出错误的代码
} catch (error) {
  // 错误处理代码
  // error 参数包含错误信息
} finally {
  // 无论是否发生错误都会执行的代码
  // 用于清理资源
}

各部分详解

1. try 块

try {
  // 主要逻辑代码
  const data = JSON.parse(userInput);
  processData(data);
}

2. catch 块

catch (error) {
  // error 对象包含错误信息
  console.error('发生错误:', error.message);
  console.error('错误类型:', error.name);
  console.error('调用栈:', error.stack);
}

3. finally 块

finally {
  // 总是会执行,无论是否发生错误
  cleanupResources();
  console.log('操作完成');
}

完整的错误对象信息

try {
  // 可能出错的代码
  undefinedFunction();
} catch (error) {
  console.log('错误名称:', error.name);        // ReferenceError
  console.log('错误信息:', error.message);     // undefinedFunction is not defined
  console.log('错误堆栈:', error.stack);       // 详细的调用栈信息
  console.log('构造函数:', error.constructor); // 错误类型
}

应用场景

1. JSON 解析

function safeParseJSON(jsonString, fallback = {}) {
  try {
    return JSON.parse(jsonString);
  } catch (error) {
    console.warn('JSON 解析失败:', error.message);
    return fallback;
  }
}

// 使用示例
const userData = safeParseJSON(localStorage.getItem('user'), { name: 'Guest' });
console.log(userData);

2. 异步操作错误处理

async function fetchWithRetry(url, retries = 3) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    if (retries > 0) {
      console.log(`请求失败,剩余重试次数: ${retries}`);
      return fetchWithRetry(url, retries - 1);
    }
    throw new Error(`最终失败: ${error.message}`);
  }
}

// 使用示例
fetchWithRetry('https://api.example.com/data')
  .then(data => console.log(data))
  .catch(error => console.error('最终错误:', error));

3. 文件操作

const fs = require('fs');

function readFileSafely(filePath) {
  try {
    const content = fs.readFileSync(filePath, 'utf8');
    return content;
  } catch (error) {
    switch (error.code) {
      case 'ENOENT':
        return `文件不存在: ${filePath}`;
      case 'EACCES':
        return `没有权限读取文件: ${filePath}`;
      default:
        return `读取文件失败: ${error.message}`;
    }
  } finally {
    console.log(`文件操作尝试完成: ${filePath}`);
  }
}

4. 数据库操作

class DatabaseService {
  async query(sql, params = []) {
    let connection;
    try {
      connection = await this.getConnection();
      const result = await connection.execute(sql, params);
      return result;
    } catch (error) {
      // 记录详细的错误信息
      this.logError('数据库查询错误', {
        sql,
        params,
        error: error.message
      });
      
      // 根据错误类型采取不同策略
      if (error.code === 'ER_LOCK_DEADLOCK') {
        // 死锁重试
        return this.retryQuery(sql, params);
      } else if (error.code === 'ER_CON_COUNT_ERROR') {
        // 连接数过多
        throw new Error('系统繁忙,请稍后重试');
      } else {
        throw error;
      }
    } finally {
      // 确保释放连接
      if (connection) {
        await connection.release();
      }
    }
  }
}

5. 第三方 API 调用

async function callExternalAPI(apiUrl, options = {}) {
  try {
    const response = await fetch(apiUrl, {
      timeout: 5000,
      ...options
    });
    
    if (!response.ok) {
      throw new Error(`API 响应错误: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
    
  } catch (error) {
    // 网络错误处理
    if (error.name === 'TypeError' && error.message.includes('fetch')) {
      throw new Error('网络连接失败,请检查网络设置');
    }
    
    // 超时错误处理
    if (error.name === 'TimeoutError') {
      throw new Error('请求超时,请稍后重试');
    }
    
    // 其他错误
    throw new Error(`API 调用失败: ${error.message}`);
  }
}

6. 输入验证和数据处理

function processUserInput(input) {
  try {
    // 验证输入
    if (typeof input !== 'string') {
      throw new TypeError('输入必须是字符串');
    }
    
    if (input.length === 0) {
      throw new Error('输入不能为空');
    }
    
    // 处理数据
    const processed = input
      .trim()
      .toLowerCase()
      .replace(/[^a-z0-9]/g, '');
    
    if (processed.length < 3) {
      throw new Error('有效字符过少');
    }
    
    return processed;
    
  } catch (error) {
    // 提供用户友好的错误信息
    const userFriendlyMessages = {
      'TypeError': '请输入有效的文本',
      '输入不能为空': '请输入内容',
      '有效字符过少': '请输入至少3个有效字符'
    };
    
    const friendlyMessage = userFriendlyMessages[error.message] || 
                           '处理输入时发生错误';
    
    return {
      success: false,
      error: friendlyMessage,
      technicalError: error.message
    };
  }
}

高级用法

1. 嵌套 try-catch

async function complexOperation() {
  try {
    // 第一步操作
    const data = await fetchData();
    
    try {
      // 第二步操作(可能独立失败)
      const processed = await processData(data);
      return processed;
    } catch (processingError) {
      // 处理第二步的特定错误
      console.error('数据处理失败:', processingError);
      return getFallbackData();
    }
    
  } catch (error) {
    // 处理整体错误
    console.error('整体操作失败:', error);
    throw error;
  }
}

2. 自定义错误类型

class ValidationError extends Error {
  constructor(field, message) {
    super(`验证失败 [${field}]: ${message}`);
    this.name = 'ValidationError';
    this.field = field;
    this.timestamp = new Date().toISOString();
  }
}

class NetworkError extends Error {
  constructor(url, status) {
    super(`网络请求失败: ${url} (状态: ${status})`);
    this.name = 'NetworkError';
    this.url = url;
    this.status = status;
  }
}

// 使用自定义错误
function validateUser(user) {
  try {
    if (!user.email) {
      throw new ValidationError('email', '邮箱不能为空');
    }
    
    if (!user.email.includes('@')) {
      throw new ValidationError('email', '邮箱格式不正确');
    }
    
    return true;
  } catch (error) {
    if (error instanceof ValidationError) {
      // 专门处理验证错误
      console.warn(`用户输入验证失败: ${error.field}`);
      return false;
    }
    throw error; // 重新抛出未知错误
  }
}

3. Promise 错误处理

// 使用 .catch() 处理 Promise 错误
fetch('/api/data')
  .then(response => response.json())
  .then(data => processData(data))
  .catch(error => {
    console.error('Promise 链错误:', error);
    return recoverFromError(error);
  });

// 在 async/await 中使用 try-catch
async function asyncOperation() {
  try {
    const result1 = await promise1();
    const result2 = await promise2(result1);
    return result2;
  } catch (error) {
    console.error('异步操作失败:', error);
    throw error;
  }
}

4. 错误边界(React 示例)

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    // 记录错误信息
    console.error('组件错误:', error, errorInfo);
    // 可以发送错误报告到服务器
    this.logErrorToService(error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return this.props.fallback || <div>Something went wrong.</div>;
    }
    
    return this.props.children;
  }
}

// 使用
<ErrorBoundary fallback={<ErrorComponent />}>
  <MyComponent />
</ErrorBoundary>

最佳实践

1. 具体的错误处理

// ❌ 不好的做法 - 过于笼统
try {
  // 各种操作
} catch (error) {
  console.error('出错啦!');
}

// ✅ 好的做法 - 具体处理
try {
  // 各种操作
} catch (error) {
  if (error instanceof TypeError) {
    console.error('类型错误:', error.message);
  } else if (error instanceof RangeError) {
    console.error('范围错误:', error.message);
  } else if (error.code === 'NETWORK_ERROR') {
    console.error('网络错误,请检查连接');
  } else {
    console.error('未知错误:', error);
    // 发送错误报告
    reportError(error);
  }
}

2. 资源清理

function useResource() {
  let resource = acquireResource();
  
  try {
    // 使用资源
    return processResource(resource);
  } finally {
    // 确保资源被释放
    releaseResource(resource);
  }
}

3. 错误传播

async function processData(data) {
  try {
    return await transformData(data);
  } catch (error) {
    // 添加上下文信息后重新抛出
    error.message = `处理数据时失败: ${error.message}`;
    throw error;
  }
}

4. 性能考虑

// ❌ 在循环中使用 try-catch(性能差)
for (let i = 0; i < largeArray.length; i++) {
  try {
    processItem(largeArray[i]);
  } catch (error) {
    // 处理错误
  }
}

// ✅ 将 try-catch 放在循环外部(性能好)
try {
  for (let i = 0; i < largeArray.length; i++) {
    processItem(largeArray[i]);
  }
} catch (error) {
  console.error('处理过程中出错:', error);
}

常见陷阱和注意事项

1. 静默吞掉错误

// ❌ 不好的做法 - 错误被静默忽略
try {
  criticalOperation();
} catch (error) {
  // 什么都没有做!
}

// ✅ 好的做法 - 至少记录错误
try {
  criticalOperation();
} catch (error) {
  console.error('关键操作失败:', error);
  // 或者重新抛出
  throw error;
}

2. 过度使用 try-catch

// ❌ 不必要的 try-catch
try {
  const length = array.length; // 这不会抛出错误
} catch (error) {
  // 永远不会执行
}

// ✅ 只在可能出错的地方使用
if (array && array.length) {
  // 安全操作
}

3. 异步错误处理

// ❌ 这无法捕获 setTimeout 中的错误
try {
  setTimeout(() => {
    throw new Error('异步错误');
  }, 1000);
} catch (error) {
  console.error('这不会执行');
}

// ✅ 在异步回调内部处理错误
setTimeout(() => {
  try {
    throw new Error('异步错误');
  } catch (error) {
    console.error('捕获到异步错误:', error);
  }
}, 1000);

总结:
try-catchJavaScript 中强大的错误处理机制,合理使用可以构建出健壮、可靠的应用程序。关键是要理解何时使用它,以及如何提供有意义的错误处理和恢复策略!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐