HarmonyOS开发中错误处理策略:网络异常统一处理

HarmonyOS开发中错误处理策略:网络异常统一处理

健壮的网络层,从优雅的错误处理开始

一、背景与动机:为什么需要统一错误处理?

你有没有遇到过这种情况:

用户点击"提交订单",页面卡住了,没有任何提示。用户疑惑地点了第二次、第三次...最后发现是网络断了,但订单已经被提交了三次。

或者这样:

typescript 复制代码
// 页面A
try {
  await api.submitOrder();
} catch (e) {
  alert('网络错误');
}

// 页面B
try {
  await api.getUser();
} catch (e) {
  alert('请求失败');
}

// 页面C
try {
  await api.getProduct();
} catch (e) {
  alert('出错了');
}

三种不同的错误提示,用户一脸懵:到底是哪里错了?怎么解决?

统一错误处理要解决的就是这个问题:

  1. 错误分类:网络错误、服务器错误、业务错误,分门别类
  2. 错误转换:将底层错误转换为用户可理解的提示
  3. 错误上报:自动记录错误日志,方便排查
  4. 错误恢复:提供重试、降级等恢复策略
  5. 用户提示:统一的 Toast、弹窗、错误页面

二、核心原理:错误分类与处理流程

错误处理不是简单的 try-catch,而是一个完整的处理链:

flowchart TB A[捕获异常] --> B{错误类型判断} B -->|网络异常| C[NetworkError] B -->|超时异常| D[TimeoutError] B -->|HTTP错误| E[HttpError] B -->|业务错误| F[BusinessError] B -->|解析错误| G[ParseError] B -->|未知错误| H[UnknownError] C --> I[错误转换] D --> I E --> I F --> I G --> I H --> I I --> J[错误日志记录] J --> K[错误上报] K --> L{需要用户干预?} L -->|是| M[显示错误提示] L -->|否| N[静默处理] M --> O{可恢复?} O -->|是| P[提供重试选项] O -->|否| Q[引导用户操作] classDef primary fill:#4A90E2,stroke:#2E5C8A,color:#fff classDef warning fill:#F5A623,stroke:#C17D10,color:#fff classDef error fill:#E74C3C,stroke:#C0392B,color:#fff classDef info fill:#7ED321,stroke:#5BA318,color:#fff class A,I,J,K primary class C,D,E,F,G,H error class L,O warning class M,N,P,Q info

三、代码实战:构建完整错误处理体系

示例1:错误类型定义

typescript 复制代码
// network/error/types.ets

/**
 * 错误码枚举
 * 定义所有可能的错误码,方便统一管理
 */
export enum ErrorCode {
  // 网络相关错误 (-1 ~ -99)
  NETWORK_ERROR = -1,        // 网络不可用
  NETWORK_TIMEOUT = -2,      // 请求超时
  NETWORK_DISCONNECT = -3,   // 网络断开
  NETWORK_SLOW = -4,         // 网络慢
  
  // HTTP协议错误 (100 ~ 599)
  HTTP_BAD_REQUEST = 400,    // 请求参数错误
  HTTP_UNAUTHORIZED = 401,   // 未授权
  HTTP_FORBIDDEN = 403,      // 禁止访问
  HTTP_NOT_FOUND = 404,      // 资源不存在
  HTTP_METHOD_NOT_ALLOWED = 405, // 方法不允许
  HTTP_REQUEST_TIMEOUT = 408, // 请求超时
  HTTP_CONFLICT = 409,       // 冲突
  HTTP_TOO_MANY_REQUESTS = 429, // 请求过多
  HTTP_INTERNAL_ERROR = 500, // 服务器内部错误
  HTTP_BAD_GATEWAY = 502,    // 网关错误
  HTTP_SERVICE_UNAVAILABLE = 503, // 服务不可用
  HTTP_GATEWAY_TIMEOUT = 504, // 网关超时
  
  // 业务错误 (1000 ~ 1999)
  BIZ_SUCCESS = 0,           // 成功
  BIZ_PARAM_ERROR = 1001,    // 参数错误
  BIZ_DATA_NOT_FOUND = 1002, // 数据不存在
  BIZ_PERMISSION_DENIED = 1003, // 权限不足
  BIZ_TOKEN_EXPIRED = 1004,  // Token过期
  BIZ_TOKEN_INVALID = 1005,  // Token无效
  BIZ_USER_BANNED = 1006,    // 用户被封禁
  BIZ_OPERATION_FAILED = 1007, // 操作失败
  
  // 客户端错误 (2000 ~ 2999)
  CLIENT_PARSE_ERROR = 2001, // 数据解析错误
  CLIENT_CACHE_ERROR = 2002, // 缓存错误
  CLIENT_STORAGE_ERROR = 2003, // 存储错误
  
  // 未知错误
  UNKNOWN = -9999
}

/**
 * 错误严重程度
 */
export enum ErrorSeverity {
  LOW = 'low',       // 低:不影响使用,静默处理
  MEDIUM = 'medium', // 中:影响当前操作,提示用户
  HIGH = 'high',     // 高:影响整体使用,弹窗提示
  CRITICAL = 'critical' // 严重:应用无法使用,阻断操作
}

/**
 * 错误分类
 */
export enum ErrorCategory {
  NETWORK = 'network',   // 网络错误
  HTTP = 'http',         // HTTP错误
  BUSINESS = 'business', // 业务错误
  CLIENT = 'client',     // 客户端错误
  UNKNOWN = 'unknown'    // 未知错误
}

/**
 * API错误基类
 * 所有网络错误的基类,包含丰富的错误信息
 */
export class ApiError extends Error {
  /** 错误码 */
  readonly code: ErrorCode;
  
  /** HTTP状态码 */
  readonly status: number;
  
  /** 错误分类 */
  readonly category: ErrorCategory;
  
  /** 严重程度 */
  readonly severity: ErrorSeverity;
  
  /** 原始错误数据 */
  readonly rawData?: any;
  
  /** 是否可重试 */
  readonly retryable: boolean;
  
  /** 用户友好的错误提示 */
  readonly userMessage: string;
  
  /** 错误发生时间 */
  readonly timestamp: number;

  constructor(config: {
    message: string;
    code: ErrorCode;
    status?: number;
    category?: ErrorCategory;
    severity?: ErrorSeverity;
    rawData?: any;
    retryable?: boolean;
    userMessage?: string;
  }) {
    super(config.message);
    this.name = 'ApiError';
    this.code = config.code;
    this.status = config.status ?? 0;
    this.category = config.category ?? ErrorCategory.UNKNOWN;
    this.severity = config.severity ?? ErrorSeverity.MEDIUM;
    this.rawData = config.rawData;
    this.retryable = config.retryable ?? false;
    this.userMessage = config.userMessage ?? config.message;
    this.timestamp = Date.now();
  }

  /**
   * 转换为JSON(用于日志记录)
   */
  toJSON(): Record<string, any> {
    return {
      name: this.name,
      message: this.message,
      code: this.code,
      status: this.status,
      category: this.category,
      severity: this.severity,
      retryable: this.retryable,
      userMessage: this.userMessage,
      timestamp: this.timestamp,
      stack: this.stack
    };
  }
}

/**
 * 网络错误
 */
export class NetworkError extends ApiError {
  constructor(message: string = '网络连接失败', code: ErrorCode = ErrorCode.NETWORK_ERROR) {
    super({
      message,
      code,
      category: ErrorCategory.NETWORK,
      severity: ErrorSeverity.HIGH,
      retryable: true,
      userMessage: '网络不给力,请检查网络设置'
    });
    this.name = 'NetworkError';
  }
}

/**
 * 超时错误
 */
export class TimeoutError extends ApiError {
  constructor(message: string = '请求超时') {
    super({
      message,
      code: ErrorCode.NETWORK_TIMEOUT,
      category: ErrorCategory.NETWORK,
      severity: ErrorSeverity.MEDIUM,
      retryable: true,
      userMessage: '请求超时,请稍后重试'
    });
    this.name = 'TimeoutError';
  }
}

/**
 * HTTP错误
 */
export class HttpError extends ApiError {
  constructor(status: number, message: string, data?: any) {
    const severity = status >= 500 ? ErrorSeverity.HIGH : ErrorSeverity.MEDIUM;
    const retryable = [408, 429, 500, 502, 503, 504].includes(status);
  
    super({
      message,
      code: status as ErrorCode,
      status,
      category: ErrorCategory.HTTP,
      severity,
      rawData: data,
      retryable,
      userMessage: HttpError.getUserMessage(status)
    });
    this.name = 'HttpError';
  }

  /**
   * 根据状态码获取用户提示
   * @private
   */
  private static getUserMessage(status: number): string {
    const messages: Record<number, string> = {
      400: '请求参数错误',
      401: '请先登录',
      403: '没有访问权限',
      404: '请求的资源不存在',
      408: '请求超时',
      429: '请求过于频繁,请稍后再试',
      500: '服务器开小差了,请稍后再试',
      502: '网关错误',
      503: '服务暂时不可用',
      504: '网关超时'
    };
    return messages[status] || `请求失败(${status})`;
  }
}

/**
 * 业务错误
 */
export class BusinessError extends ApiError {
  constructor(code: number, message: string, data?: any) {
    super({
      message,
      code: code as ErrorCode,
      category: ErrorCategory.BUSINESS,
      severity: ErrorSeverity.MEDIUM,
      rawData: data,
      retryable: false,
      userMessage: message
    });
    this.name = 'BusinessError';
  }
}

示例2:错误处理器

typescript 复制代码
// network/error/ErrorHandler.ets
import { 
  ApiError, NetworkError, TimeoutError, HttpError, BusinessError,
  ErrorCode, ErrorCategory, ErrorSeverity 
} from './types';
import { promptAction } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';

/**
 * 错误处理器配置
 */
export interface ErrorHandlerConfig {
  /** 是否显示错误提示 */
  showToast?: boolean;
  /** 是否记录日志 */
  logError?: boolean;
  /** 是否上报错误 */
  reportError?: boolean;
  /** 自定义错误提示映射 */
  customMessages?: Map<ErrorCode, string>;
  /** 错误回调 */
  onError?: (error: ApiError) => void;
}

/**
 * 错误处理器
 * 统一处理所有网络错误
 */
export class ErrorHandler {
  private config: ErrorHandlerConfig;

  constructor(config: ErrorHandlerConfig = {}) {
    this.config = {
      showToast: true,
      logError: true,
      reportError: true,
      ...config
    };
  }

  /**
   * 处理错误
   * @param error 原始错误
   * @returns 转换后的ApiError
   */
  handle(error: any): ApiError {
    // 1. 转换为ApiError
    const apiError = this.transform(error);

    // 2. 记录日志
    if (this.config.logError) {
      this.logError(apiError);
    }

    // 3. 上报错误
    if (this.config.reportError) {
      this.reportError(apiError);
    }

    // 4. 显示用户提示
    if (this.config.showToast && apiError.severity !== ErrorSeverity.LOW) {
      this.showUserMessage(apiError);
    }

    // 5. 执行自定义回调
    if (this.config.onError) {
      this.config.onError(apiError);
    }

    return apiError;
  }

  /**
   * 转换错误为ApiError
   * @private
   */
  private transform(error: any): ApiError {
    // 已经是ApiError,直接返回
    if (error instanceof ApiError) {
      return error;
    }

    // 处理原生网络错误
    if (this.isNetworkError(error)) {
      return new NetworkError();
    }

    // 处理超时错误
    if (this.isTimeoutError(error)) {
      return new TimeoutError();
    }

    // 处理HTTP错误响应
    if (this.isHttpError(error)) {
      return new HttpError(
        error.responseCode || error.status,
        error.message || 'HTTP Error',
        error.result || error.data
      );
    }

    // 处理业务错误
    if (this.isBusinessError(error)) {
      return new BusinessError(
        error.code,
        error.message,
        error.data
      );
    }

    // 未知错误
    return new ApiError({
      message: error.message || '未知错误',
      code: ErrorCode.UNKNOWN,
      category: ErrorCategory.UNKNOWN,
      severity: ErrorSeverity.MEDIUM,
      rawData: error
    });
  }

  /**
   * 判断是否为网络错误
   * @private
   */
  private isNetworkError(error: any): boolean {
    // HarmonyOS网络错误码
    const networkErrorCodes = [2300001, 2300002, 2300003];
    return networkErrorCodes.includes(error.code) || 
           error.message?.includes('Network') ||
           error.message?.includes('网络');
  }

  /**
   * 判断是否为超时错误
   * @private
   */
  private isTimeoutError(error: any): boolean {
    return error.code === 2300002 ||
           error.message?.includes('Timeout') ||
           error.message?.includes('超时');
  }

  /**
   * 判断是否为HTTP错误
   * @private
   */
  private isHttpError(error: any): boolean {
    return error.responseCode >= 400 || error.status >= 400;
  }

  /**
   * 判断是否为业务错误
   * @private
   */
  private isBusinessError(error: any): boolean {
    return error.code !== undefined && 
           error.code !== 0 && 
           error.message !== undefined;
  }

  /**
   * 记录错误日志
   * @private
   */
  private logError(error: ApiError): void {
    const logContent = `
┌────────────────────────────────────────
│ [错误日志] ${new Date(error.timestamp).toISOString()}
├────────────────────────────────────────
│ 类型: ${error.name}
│ 分类: ${error.category}
│ 严重程度: ${error.severity}
│ 错误码: ${error.code}
│ HTTP状态: ${error.status}
│ 消息: ${error.message}
│ 用户提示: ${error.userMessage}
│ 可重试: ${error.retryable}
│ 堆栈: ${error.stack?.split('\n').slice(0, 3).join('\n')}
└────────────────────────────────────────
    `;

    // 根据严重程度选择日志级别
    if (error.severity === ErrorSeverity.CRITICAL) {
      hilog.error(0x0000, 'NetworkError', logContent);
    } else if (error.severity === ErrorSeverity.HIGH) {
      hilog.warn(0x0000, 'NetworkError', logContent);
    } else {
      hilog.info(0x0000, 'NetworkError', logContent);
    }
  }

  /**
   * 上报错误到服务器
   * @private
   */
  private reportError(error: ApiError): void {
    // 这里可以集成Sentry、Bugly等错误监控平台
    // 简化示例:只记录到本地
    console.info('[ErrorHandler] 错误已上报:', error.code);
  
    // 实际项目中可以:
    // 1. 收集设备信息、用户信息
    // 2. 打包错误数据
    // 3. 发送到错误监控平台
  }

  /**
   * 显示用户提示
   * @private
   */
  private showUserMessage(error: ApiError): void {
    // 使用自定义消息或默认消息
    let message = this.config.customMessages?.get(error.code) || error.userMessage;

    // 根据严重程度选择提示方式
    if (error.severity === ErrorSeverity.CRITICAL) {
      // 严重错误:弹窗提示
      promptAction.showDialog({
        title: '错误',
        message: message,
        buttons: [{ text: '确定', color: '#E74C3C' }]
      });
    } else {
      // 普通错误:Toast提示
      promptAction.showToast({
        message: message,
        duration: 3000
      });
    }
  }
}

示例3:错误处理拦截器

typescript 复制代码
// network/error/ErrorInterceptor.ets
import { Interceptor, Response, RequestConfig } from '../types';
import { ErrorHandler, ApiError } from './ErrorHandler';

/**
 * 错误处理拦截器
 * 拦截所有请求错误,统一处理
 */
export class ErrorInterceptor implements Interceptor {
  private errorHandler: ErrorHandler;

  constructor(errorHandler: ErrorHandler) {
    this.errorHandler = errorHandler;
  }

  /**
   * 请求拦截:无操作
   */
  async beforeRequest(config: RequestConfig): Promise<RequestConfig> {
    return config;
  }

  /**
   * 响应拦截:检查错误
   */
  async afterResponse<T>(response: Response<T>): Promise<Response<T>> {
    // 检查HTTP状态码
    if (response.status >= 200 && response.status < 300) {
      // 请求成功,检查业务状态码
      const data = response.data as any;
    
      if (data && typeof data === 'object' && 'code' in data) {
        // 后端统一返回格式:{ code, message, data }
        if (data.code !== 0) {
          // 业务错误
          throw this.errorHandler.handle({
            code: data.code,
            message: data.message || data.msg || '业务错误',
            data: data.data
          });
        }
      
        // 业务成功,返回真实数据
        return {
          ...response,
          data: data.data as T
        };
      }
    
      // 直接返回数据
      return response;
    }

    // HTTP错误,抛出
    throw this.errorHandler.handle({
      responseCode: response.status,
      message: 'HTTP Error',
      result: response.data
    });
  }
}

示例4:错误边界组件

typescript 复制代码
// components/ErrorBoundary.ets
import { ApiError, ErrorCategory } from '../network/error/types';
import { promptAction } from '@kit.ArkUI';

/**
 * 错误边界组件
 * 用于包裹可能出错的UI组件,提供降级显示
 */
@Component
export struct ErrorBoundary {
  @Prop hasError: boolean = false;
  @Prop error: ApiError | null = null;
  
  @BuilderParam defaultContent: () => void;
  @BuilderParam errorContent?: () => void;

  build() {
    if (this.hasError && this.error) {
      // 显示错误UI
      if (this.errorContent) {
        this.errorContent();
      } else {
        this.defaultErrorUI();
      }
    } else {
      // 显示正常内容
      this.defaultContent();
    }
  }

  /**
   * 默认错误UI
   */
  @Builder
  defaultErrorUI() {
    Column() {
      // 错误图标
      Image($r('app.media.ic_error'))
        .width(80)
        .height(80)
        .margin({ bottom: 16 })

      // 错误提示
      Text(this.error?.userMessage || '出错了')
        .fontSize(16)
        .fontColor('#666666')
        .margin({ bottom: 24 })

      // 重试按钮(如果可重试)
      if (this.error?.retryable) {
        Button('重试')
          .type(ButtonType.Capsule)
          .backgroundColor('#4A90E2')
          .fontColor(Color.White)
          .onClick(() => {
            // 通知父组件重试
            this.hasError = false;
          })
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#F5F5F5')
  }

  /**
   * 捕获错误
   */
  catch(error: ApiError) {
    this.error = error;
    this.hasError = true;
  }

  /**
   * 重置错误状态
   */
  reset() {
    this.error = null;
    this.hasError = false;
  }
}

示例5:页面中的错误处理实战

typescript 复制代码
// pages/UserPage.ets
import { UserAPI } from '../api/UserAPI';
import { ApiError, NetworkError } from '../network/error/types';
import { ErrorHandler } from '../network/error/ErrorHandler';

@Entry
@Component
struct UserPage {
  @State user: User | null = null;
  @State loading: boolean = false;
  @State error: ApiError | null = null;

  private userApi: UserAPI = new UserAPI();
  private errorHandler: ErrorHandler = new ErrorHandler({
    showToast: false, // 页面自己控制提示
    onError: (err) => {
      this.error = err;
    }
  });

  aboutToAppear() {
    this.loadUser();
  }

  /**
   * 加载用户信息
   */
  async loadUser() {
    this.loading = true;
    this.error = null;

    try {
      const response = await this.userApi.getCurrentUser();
      this.user = response.data;
    } catch (e) {
      // 统一错误处理
      const apiError = this.errorHandler.handle(e);
      this.error = apiError;
    } finally {
      this.loading = false;
    }
  }

  build() {
    Column() {
      if (this.loading) {
        // 加载中
        this.loadingUI();
      } else if (this.error) {
        // 错误状态
        this.errorUI();
      } else if (this.user) {
        // 正常显示
        this.contentUI();
      }
    }
    .width('100%')
    .height('100%')
  }

  /**
   * 加载UI
   */
  @Builder
  loadingUI() {
    Column() {
      LoadingProgress()
        .width(48)
        .height(48)
        .color('#4A90E2')
    
      Text('加载中...')
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 16 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  /**
   * 错误UI
   */
  @Builder
  errorUI() {
    Column() {
      // 根据错误类型显示不同图标
      if (this.error?.category === 'network') {
        Image($r('app.media.ic_network_error'))
          .width(100)
          .height(100)
      } else {
        Image($r('app.media.ic_error'))
          .width(100)
          .height(100)
      }

      Text(this.error?.userMessage || '出错了')
        .fontSize(16)
        .fontColor('#666666')
        .margin({ top: 16, bottom: 24 })

      // 重试按钮
      if (this.error?.retryable) {
        Button('重新加载')
          .type(ButtonType.Capsule)
          .backgroundColor('#4A90E2')
          .fontColor(Color.White)
          .width(120)
          .onClick(() => {
            this.loadUser();
          })
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#F5F5F5')
  }

  /**
   * 内容UI
   */
  @Builder
  contentUI() {
    Column() {
      // 头像
      Image(this.user?.avatar)
        .width(80)
        .height(80)
        .borderRadius(40)
        .margin({ bottom: 16 })

      // 用户名
      Text(this.user?.name || '')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      // 邮箱
      Text(this.user?.email || '')
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 8 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

四、踩坑与注意事项

坑1:错误信息泄露

问题:直接把后端错误消息显示给用户,可能泄露敏感信息。

解决:使用用户友好的提示:

typescript 复制代码
// ❌ 错误:直接显示后端消息
alert(error.message); // "SQL syntax error near 'select'"

// ✅ 正确:使用用户友好提示
alert(error.userMessage); // "服务器开小差了,请稍后再试"

坑2:错误吞没

问题:catch 块中没有处理错误,导致错误被"吞没"。

typescript 复制代码
// ❌ 错误:空catch
try {
  await api.getData();
} catch (e) {
  // 什么都不做,错误被吞没
}

// ✅ 正确:至少记录日志
try {
  await api.getData();
} catch (e) {
  console.error('请求失败:', e);
  throw e; // 继续抛出,让上层处理
}

坑3:异步错误未捕获

问题:Promise 的错误没有被 try-catch 捕获。

typescript 复制代码
// ❌ 错误:async函数外的错误不会被捕获
try {
  api.getData().then(data => {
    // 这里抛错不会被外层catch捕获
    throw new Error('处理失败');
  });
} catch (e) {
  // 捕获不到
}

// ✅ 正确:使用async/await
try {
  const data = await api.getData();
  // 处理数据
} catch (e) {
  // 可以捕获
}

坑4:错误循环上报

问题:错误上报接口失败,又触发错误处理,导致循环。

解决:错误上报接口跳过错误处理:

typescript 复制代码
// 错误上报使用独立的http实例,不走拦截器
const reportHttp = http.createHttp();
// 不添加ErrorInterceptor

五、HarmonyOS 6 适配要点

1. hilog API 增强

typescript 复制代码
// HarmonyOS 6 支持更丰富的日志功能
import { hilog } from '@kit.PerformanceAnalysisKit';

// 支持格式化参数
hilog.error(0x0000, 'MyTag', 'Error occurred: %{public}s, code: %{public}d', 
  error.message, error.code);

// 支持不同日志级别
hilog.debug(0x0000, 'MyTag', 'Debug message');
hilog.info(0x0000, 'MyTag', 'Info message');
hilog.warn(0x0000, 'MyTag', 'Warning message');
hilog.error(0x0000, 'MyTag', 'Error message');
hilog.fatal(0x0000, 'MyTag', 'Fatal message');

2. promptAction 增强

typescript 复制代码
// HarmonyOS 6 Toast支持更多配置
promptAction.showToast({
  message: '操作成功',
  duration: 2000,
  bottom: 100,  // 距离底部距离
  showMode: promptAction.ToastShowMode.TOP_MOVED
});

// Dialog支持更多按钮样式
promptAction.showDialog({
  title: '确认删除',
  message: '删除后无法恢复',
  buttons: [
    { text: '取消', color: '#666666' },
    { text: '删除', color: '#E74C3C' }
  ]
});

3. 错误码标准化

typescript 复制代码
// HarmonyOS 6 定义了更详细的错误码
// 网络错误码范围:2300001 - 2300999
// 2300001: 网络不可用
// 2300002: 连接超时
// 2300003: 协议错误
// 2300004: URL格式错误
// 2300005: DNS解析失败
// ...

// 可以根据系统错误码映射
if (error.code >= 2300001 && error.code <= 2300999) {
  // 网络相关错误
}

六、总结一下下

统一错误处理是网络层的"安全网",让应用在异常情况下也能优雅应对:

错误类型 分类 严重程度 可重试 用户提示
NetworkError network HIGH 网络不给力
TimeoutError network MEDIUM 请求超时
HttpError(401) http HIGH 请先登录
HttpError(404) http MEDIUM 资源不存在
HttpError(500) http HIGH 服务器开小差了
BusinessError business MEDIUM 后端消息

记住几个原则:

  • ✅ 错误要分类,不同类型不同处理
  • ✅ 提示要友好,别让用户看技术错误
  • ✅ 日志要完整,方便排查问题
  • ✅ 上报要及时,监控线上质量
  • ✅ 降级要优雅,别让应用崩溃

下一篇我们深入请求取消与并发,看看 AbortController 的妙用。


💡 最佳实践提示:建议在项目中建立错误码文档,统一管理所有错误码和对应的处理策略,方便团队协作。

相关推荐
小小杨树3 小时前
读懂色彩:拍照调色不再难
算法·计算机视觉·配色
JieE21219 小时前
LeetCode 226. 翻转二叉树|JS 递归超详细拆解,二叉树入门经典题
javascript·算法
JieE21219 小时前
LeetCode 104. 二叉树的最大深度|递归思路超详细拆解
javascript·算法
vivo互联网技术1 天前
CVPR 2026 | 全新强化学习框架 BeautyGRPO:重塑真实人像
算法·大模型·cvpr·影像
Darling噜啦啦1 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
用户497863050731 天前
(一)小红的数组操作
算法·编程语言
怕浪猫1 天前
Electron 系列文章封面图
算法·架构·前端框架
徐小夕1 天前
JitWord 3.0 正式发布,高精度Word异构解析+复杂组件兼容,打造web端协同Word编辑器
前端·vue.js·算法