HarmonyOS 開發中的錯誤處理策略:網路異常統一處理

HarmonyOS 開發中的錯誤處理策略:網路異常統一處理

穩健的網路層,從優雅的錯誤處理開始

一、背景與動機:為什麼需要統一錯誤處理?

你有沒有遇過這種情況:

使用者點擊「提交訂單」,頁面卡住了,沒有任何提示。使用者疑惑地點了第二次、第三次...最後發現是網路斷了,但訂單已經被提交了三次。

或者這樣:

// 頁面 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,而是一個完整的處理鏈:

三、程式碼實戰:建構完整錯誤處理體系

範例 1:錯誤型別定義

// 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:錯誤處理器

// 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:錯誤處理攔截器

// 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:錯誤邊界元件

// 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:頁面中的錯誤處理實戰

// 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:錯誤資訊外洩

問題:直接把後端錯誤訊息顯示給使用者,可能洩漏敏感資訊。

解決:使用使用者友善的提示:

// ❌ 錯誤:直接顯示後端訊息
alert(error.message); // "SQL syntax error near 'select'"

// ✅ 正確:使用使用者友善提示
alert(error.userMessage); // "伺服器忙碌中,請稍後再試"

坑 2:錯誤吞掉

問題:catch 區塊中沒有處理錯誤,導致錯誤被「吞掉」。

// ❌ 錯誤:空 catch
try {
  await api.getData();
} catch (e) {
  // 什麼都不做,錯誤被吞掉
}

// ✅ 正確:至少記錄日誌
try {
  await api.getData();
} catch (e) {
  console.error('請求失敗:', e);
  throw e; // 繼續拋出,讓上層處理
}

坑 3:非同步錯誤未捕獲

問題:Promise 的錯誤沒有被 try-catch 捕獲。

// ❌ 錯誤:async 函式外的錯誤不會被捕獲
try {
  api.getData().then(data => {
    // 這裡拋錯不會被外層 catch 捕獲
    throw new Error('處理失敗');
  });
} catch (e) {
  // 捕獲不到
}

// ✅ 正確:使用 async/await
try {
  const data = await api.getData();
  // 處理資料
} catch (e) {
  // 可以捕獲
}

坑 4:錯誤循環上報

問題:錯誤上報介面失敗,又觸發錯誤處理,導致循環。

解決:錯誤上報介面跳過錯誤處理:

// 錯誤上報使用獨立的 http 實例,不走攔截器
const reportHttp = http.createHttp();
// 不添加 ErrorInterceptor

五、HarmonyOS 6 適配要點

1. hilog API 增強

// 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 增強

// 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. 錯誤碼標準化

// HarmonyOS 6 定義了更詳細的錯誤碼
// 網路錯誤碼範圍:2300001 - 2300999
// 2300001: 網路不可用
// 2300002: 連線逾時
// 2300003: 協定錯誤
// 2300004: URL 格式錯誤
// 2300005: DNS 解析失敗
// ...

// 可以根據系統錯誤碼對應
if (error.code >= 2300001 && error.code <= 2300999) {
  // 網路相關錯誤
}

六、總結一下下

統一錯誤處理是網路層的「安全網」,讓應用在異常情況下也能優雅應對:

錯誤類型分類嚴重程度可重試使用者提示NetworkErrornetworkHIGH✅網路不太穩定TimeoutErrornetworkMEDIUM✅請求逾時HttpError(401)httpHIGH❌請先登入HttpError(404)httpMEDIUM❌資源不存在HttpError(500)httpHIGH✅伺服器忙碌中BusinessErrorbusinessMEDIUM❌後端訊息記住幾個原則:

  • ✅ 錯誤要分類,不同型別不同處理
  • ✅ 提示要友善,別讓使用者看技術錯誤
  • ✅ 日誌要完整,方便排查問題
  • ✅ 上報要及時,監控線上品質
  • ✅ 降級要優雅,別讓應用崩潰

下一篇我們深入請求取消與並發,看看 AbortController 的妙用。


💡 最佳實踐提示:建議在專案中建立錯誤碼文件,統一管理所有錯誤碼和對應的處理策略,方便團隊協作。


原文出處:https://juejin.cn/post/7652636952518754340


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝16   💬1   ❤️1
505
🥈
我愛JS
📝1   ❤️1
29
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登