Mục lục

API Error Handling: Chiến lược Global Interceptor

Đừng try-catch lặt vặt. Xây dựng lớp xử lý lỗi API tập trung: Tự động Refresh Token khi 401, Retry khi mạng lag, và hiển thị Toast thông báo lỗi chuẩn hóa.

Vấn đề: Bạn có 50 API calls. Bạn không muốn copy-paste đoạn code check status === 401 ở 50 nơi.

Giải pháp: Xử lý tập trung (Centralized Handling) tại tầng Networking (Axios Interceptors hoặc Fetch Wrapper).

1. Phân loại lỗi API

Trước khi xử lý, phải biết nó là lỗi gì:

  1. Network Error: Mất mạng, DNS lỗi. -> Action: Retry & Show Offline Toast.
  2. 4xx Client Error:
    • 401 Unauthorized: Token hết hạn. -> Action: Refresh Token hoặc Logout.
    • 403 Forbidden: Không có quyền. -> Action: Redirect trang 403.
    • 422 Validation: Form sai dữ liệu. -> Action: Map lỗi vào Input Field.
  3. 5xx Server Error: Backend sập. -> Action: Show "Server Busy" Toast.

2. Axios Interceptor Pattern

Đây là pattern phổ biến nhất và mạnh nhất.

ts:
import axios from "axios";
import { toast } from "sonner"; // Thư viện Toast xịn

const api = axios.create({ baseURL: "/api" });

// Response Interceptor
api.interceptors.response.use(
  (response) => response.data, // Trả về data sạch luôn
  async (error) => {
    const originalRequest = error.config;

    // 1. Xử lý mạng
    if (!error.response) {
      toast.error("Vui lòng kiểm tra kết nối mạng!");
      return Promise.reject(error);
    }

    const { status, data } = error.response;

    // 2. Xử lý 401 (Refresh Token)
    if (status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      try {
        const newToken = await refreshTokenService();
        axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
        // Gọi lại request cũ với token mới
        return api(originalRequest);
      } catch (refreshError) {
        // Refresh cũng tạch -> Logout
        window.location.href = "/login";
        return Promise.reject(refreshError);
      }
    }

    // 3. Xử lý Global Error (500, 403)
    if (status >= 500) {
      toast.error("Hệ thống đang bảo trì. Vui lòng thử lại sau.");
    } else if (status === 403) {
      toast.error("Bạn không có quyền thực hiện hành động này.");
    }

    // Ném lỗi tiếp để Component xử lý (nếu cần xử lý riêng UI như form error)
    return Promise.reject(error);
  }
);

3. Custom Fetch Wrapper (Cho Next.js App Router)

Vì Next.js khuyến khích dùng fetch (để caching), ta cần bọc nó lại.

ts:
async function http<T>(path: string, config?: RequestInit): Promise<T> {
  const request = new Request(path, config);
  const response = await fetch(request);

  if (!response.ok) {
    // Tự định nghĩa Error Object chuẩn
    const errorBody = await response.json().catch(() => ({}));
    const error = new Error(errorBody.message || "Unknown Error") as any;
    error.status = response.status;
    error.info = errorBody;
    
    // Global handling
    if (response.status === 401) {
       // Server Component không redirect window được -> Redirect() của Next.js
    }
    
    throw error;
  }

  return response.json();
}

Kết luận

"Smart API Layer" phải tự lo liệu các vấn đề hạ tầng (Auth, Network, Server Crash) để Dev viết UI chỉ cần quan tâm đến lỗi logic nghiệp vụ.

Quảng cáo
mdhorizontal