Trước hết, một cạm bẫy của fetch: nó không reject với status 4xx/5xx — chỉ reject khi lỗi mạng thật sự. Phải tự kiểm if (!res.ok) throw ..., nếu không mã 500 sẽ lọt qua như thành công.
Phân loại lỗi để xử lý khác nhau:
- Lỗi mạng / offline (
fetchreject): thường tạm thời → retry hợp lý; báo "kiểm tra kết nối". - 4xx (lỗi client):
401→ refresh token hoặc đăng nhập lại;403→ không đủ quyền;404→ hiện empty/not-found;422→ lỗi validation, gắn message vào field. Không retry 4xx — request sai thì thử lại vẫn sai. - 5xx / timeout: lỗi phía server tạm thời → retry có exponential backoff (ví dụ 1s, 2s, 4s + jitter) và giới hạn số lần (2–3). Không retry ngay lập tức liên tục kẻo làm server đang quá tải nặng thêm.
js
async function fetchWithRetry(url, tries = 3) {
for (let i = 0; i < tries; i++) {
try {
const res = await fetch(url, { signal: AbortSignal.timeout(5000) })
if (res.status < 500) return res // do not retry 4xx
throw new Error(`HTTP ${res.status}`)
} catch (e) {
if (i === tries - 1) throw e
await new Promise((r) => setTimeout(r, 2 ** i * 1000 + Math.random() * 200)) // backoff + jitter
}
}
}Cấp độ hiển thị lỗi:
- Lỗi cục bộ trong một widget → xử lý inline: state
error+ nút retry, phần còn lại của trang vẫn dùng được. - Lỗi khi render (bug JS) → Error Boundary bọc quanh subtree để một component vỡ không làm trắng cả app; hiện fallback UI. Lưu ý error boundary không bắt lỗi trong event handler hay code async — những chỗ đó phải try/catch thủ công.
Nếu dự án dùng React Query, phần retry/backoff cấu hình sẵn qua retry/retryDelay — nhớ chỉnh để không retry 4xx. Luôn timeout request (AbortSignal.timeout()), đừng để người dùng chờ vô hạn.