异常处理

120 阅读3分钟

一、异常处理基础

1. 错误类型

JavaScript 内置了多种错误类型:

  • Error:通用错误类型
  • SyntaxError:语法错误
  • ReferenceError:引用未定义变量
  • TypeError:类型错误(如调用不存在的方法)
  • RangeError:数值超出范围
  • URIError:URI 处理错误
  • EvalError:eval 函数错误(现代JS中很少见)

2. try-catch-finally 结构

try {
  // 可能出错的代码
  riskyOperation();
} catch (error) {
  // 错误处理
  console.error('Error occurred:', error.message);
} finally {
  // 无论是否出错都会执行
  cleanup();
}

3. throw 抛出异常

function validateInput(input) {
  if (!input) {
    throw new Error('Input cannot be empty');
  }
  if (input.length < 5) {
    throw new RangeError('Input must be at least 5 characters');
  }
}

二、实际应用场景

场景 1:API 请求错误处理

async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Failed to fetch user:', error);
    
    // 根据错误类型采取不同措施
    if (error instanceof TypeError) {
      // 网络错误或CORS问题
      showNetworkErrorToast();
    } else if (error.message.includes('404')) {
      // 用户不存在
      showUserNotFoundMessage();
    } else {
      // 其他错误
      showGenericError();
    }
    
    // 返回默认值或重新抛出
    return { name: 'Guest' };
  }
}

场景 2:表单验证

function validateRegistrationForm(formData) {
  try {
    if (!formData.username) {
      throw new Error('Username is required');
    }
    
    if (formData.password.length < 8) {
      throw new Error('Password must be at least 8 characters');
    }
    
    if (formData.age < 18) {
      throw new RangeError('You must be at least 18 years old');
    }
    
    return true;
  } catch (error) {
    // 显示友好的错误信息给用户
    displayFormError(error.message);
    return false;
  }
}

场景 3:JSON 解析安全处理

function safeJsonParse(jsonString) {
  try {
    return JSON.parse(jsonString);
  } catch (error) {
    if (error instanceof SyntaxError) {
      console.warn('Invalid JSON string:', jsonString);
      return null;
    }
    // 其他类型的错误重新抛出
    throw error;
  }
}

// 使用示例
const userData = safeJsonParse(localStorage.getItem('user'));
if (!userData) {
  // 处理无效JSON情况
}

场景 4:异步操作中的错误处理

async function processOrder(orderId) {
  try {
    const order = await getOrder(orderId);
    const payment = await processPayment(order);
    await sendConfirmation(order, payment);
  } catch (error) {
    console.error('Order processing failed:', error);
    
    // 记录错误到监控系统
    logErrorToService(error);
    
    // 尝试恢复操作
    await sendFailureNotification(orderId);
    
    // 重新抛出以便上层处理
    throw new Error('Order processing failed', { cause: error });
  }
}

// 调用处
processOrder(12345).catch(error => {
  showErrorToUser(error.message);
});

场景 5:第三方库错误封装

class DatabaseClient {
  constructor() {
    this.driver = require('unstable-db-driver');
  }
  
  async query(sql) {
    try {
      return await this.driver.execute(sql);
    } catch (error) {
      // 将第三方库特定错误转换为应用标准错误
      if (error.code === 'DB_CONN_TIMEOUT') {
        throw new Error('Database connection timeout');
      } else if (error.code === 'QUERY_SYNTAX') {
        throw new SyntaxError(`Invalid SQL query: ${sql}`);
      } else {
        throw new Error('Database operation failed', { cause: error });
      }
    }
  }
}

三、高级异常处理技巧

1. 自定义错误类型

class AuthenticationError extends Error {
  constructor(message, metadata = {}) {
    super(message);
    this.name = 'AuthenticationError';
    this.metadata = metadata;
    this.isOperational = true; // 标记为可操作错误
  }
}

// 使用
try {
  login(user);
} catch (error) {
  if (error instanceof AuthenticationError) {
    // 专门处理认证错误
    showLoginError(error.message);
  } else {
    throw error;
  }
}

2. 错误边界(React 场景)

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    logErrorToService(error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return <FallbackUI />;
    }
    return this.props.children;
  }
}

// 使用
<ErrorBoundary>
  <UserProfile />
</ErrorBoundary>

3. Promise 链中的错误处理

function fetchWithRetry(url, retries = 3) {
  return fetch(url)
    .catch(error => {
      if (retries <= 0) throw error;
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(fetchWithRetry(url, retries - 1));
        }, 1000);
      });
    });
}

// 使用
fetchWithRetry('/api/data')
  .then(processData)
  .catch(error => {
    console.error('Failed after retries:', error);
  });

4. Node.js 中的错误处理最佳实践

// 处理未捕获的Promise异常
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});

// 处理未捕获的异常
process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
  // 对于不可恢复的错误,最好退出进程
  if (!isOperationalError(error)) {
    process.exit(1);
  }
});

// 区分操作错误(可恢复)和编程错误(应崩溃)
function isOperationalError(error) {
  return (error instanceof BaseError && error.isOperational);
}

四、异常处理最佳实践

  1. 不要静默捕获所有错误:避免空的 catch 块,至少要记录错误

  2. 错误信息要具体

    // 不好
    throw new Error('Invalid input');
    
    // 好
    throw new Error('Email address must contain @ symbol');
    
  3. 区分可恢复错误和致命错误:可恢复错误应该被捕获处理,致命错误应该记录并终止

  4. 错误日志要包含上下文信息

    catch (error) {
      console.error('Failed to process order', {
        error: error.message,
        stack: error.stack,
        orderId: order.id,
        timestamp: new Date().toISOString()
      });
    }
    
  5. 前端用户友好的错误提示:将技术性错误转换为用户能理解的信息

  6. 使用错误监控服务:如 Sentry、Bugsnag 等

五、实际场景总结

  1. 用户输入验证:提供即时反馈,防止无效数据进入系统

  2. 网络请求:处理超时、服务器错误、无效响应

  3. 第三方集成:封装不可控的外部库错误

  4. 关键业务流程:如支付处理,需要详细的错误日志和恢复机制

  5. 异步操作:Promise 和 async/await 的错误传播特性