Bỏ qua tới nội dung

Hướng dẫn

Error boundary của React và lỗi async: thí nghiệm bắt được gì

Error boundary bắt ít lỗi hơn nhiều người nghĩ. Mình ném lỗi ở tám chỗ trong bản build production React 19 và ghi lại boundary, root callback và window mỗi bên thấy gì.

Andrew Babo5 phút đọc

Chia sẻ
Trong bài này
Error boundary của React và lỗi async: thí nghiệm bắt được gì

Error boundary của React bắt lỗi ném ra trong lúc render, và lỗi ném ra trong action của useTransition. Chỉ vậy thôi. Lỗi trong handler, timer và promise đi vòng qua nó và chỉ tới sự kiện của window, còn lỗi boundary đã bắt thì không bao giờ tới window.

Vì vậy công cụ theo dõi lỗi chỉ nghe trên window sẽ bỏ sót mọi lỗi mà boundary bắt được. Công cụ chỉ dựa vào boundary thì bỏ sót mọi lỗi trong handler. Bạn cần cả hai, cộng với các root callback của React 19.

Mình thử tám trường hợp vào ngày 26/10/2026 trên bản build production React 19.2 chạy trong Chromium. Mỗi trường hợp chạy ba lần với tuỳ chọn root mặc định của React và ba lần khi đặt đủ ba root callback. Lần nào kết quả cũng giống nhau.

Trang thử#

Trang có một class error boundary bọc quanh một hàng nút. Mỗi nút hỏng theo một kiểu. Trang cũng tự lắng nghe window error và unhandledrejection, để mình thấy được công cụ theo dõi lỗi trên trình duyệt sẽ nhận gì.

TSX
const root = createRoot(container, {  onCaughtError: (error) => note("root.onCaughtError", error),  onUncaughtError: (error) => note("root.onUncaughtError", error),  onRecoverableError: (error) => note("root.onRecoverableError", error),});

Cấu hình thứ hai không truyền tuỳ chọn nào, nên React dùng mặc định.

Mỗi bên thấy gì#

Tám kiểu lỗi trong React và cái gì thấy được từng kiểu
Thí nghiệm 26/10/2026 · React 19.2.8 bản build production, Chromium · mỗi kiểu và cấu hình 3 lần
Chỗ ném lỗiBoundaryCó đặt root callbackMặc địnhTrang
Render, bên trong boundarybắt đượconCaughtErrorkhông gì khácgiao diện dự phòng
Render, không có boundary bên trênkhôngchỉ onUncaughtErrorwindow errortrắng
Handler onClickkhôngwindow errorwindow errorkhông đổi
Callback của setTimeoutkhôngwindow errorwindow errorkhông đổi
Promise được await trong handlerkhôngwindow unhandledrejectionnhư vậykhông đổi
startTransition độc lập, asynckhôngwindow errorwindow errorkhông đổi
startTransition độc lập, synckhôngwindow errorwindow errorkhông đổi
Action của useTransitionbắt đượconCaughtErrorkhông gì khácgiao diện dự phòng

onRecoverableError không lần nào chạy: nó dành cho lỗi React tự phục hồi được, như lệch hydration, còn trang này chỉ render trên trình duyệt.

Ba điều bảng này cho thấy#

Boundary bắt ít hơn cái tên gợi ý#

Tài liệu React liệt kê những gì boundary không bắt, trong đó có “Event handlers” và code bất đồng bộ. Thí nghiệm khớp với danh sách đó. Năm trong tám trường hợp đi thẳng qua boundary, và cả năm lần trang vẫn y nguyên. Người dùng bấm một nút hỏng sẽ chẳng thấy gì.

Lỗi đã bắt thì window không thấy#

Khi boundary bắt được lỗi, không có gì tới window ở cả hai cấu hình. Mặc định React ghi lỗi đã bắt ra console, mà console thì không phải là báo lỗi. Nếu công cụ theo dõi lỗi chỉ móc vào window.onerror, nó không bao giờ biết tới những lỗi boundary đã xử lý.

Đó là lý do React 19 thêm hai tuỳ chọn root “to complement onRecoverableError”: onCaughtError và onUncaughtError. Hãy báo lỗi từ đó, hoặc từ componentDidCatch trong boundary của bạn.

Đặt onUncaughtError là giành luôn đường đi của window#

Với tuỳ chọn mặc định, lỗi render không có boundary bên trên tới được window error, và cả trang trắng. Khi đặt onUncaughtError, cùng lỗi đó chỉ tới callback này. Nếu bạn đặt nó để ghi log mà quên báo lỗi, bạn mất đúng loại lỗi tệ nhất, loại làm trắng trang.

Bất ngờ với transition#

Tài liệu về boundary có thêm một ngoại lệ cho quy tắc async: “Errors thrown inside the transition function are caught by error boundaries”. Trong thí nghiệm, điều đó đúng với startTransition do hook useTransition trả về. startTransition độc lập import từ react thì gửi cùng lỗi đó, async hay sync, tới window error, và boundary không hiện gì.

Mình không nói tài liệu sai; mình chỉ thử một phiên bản trên một trình duyệt. Nếu bạn dựa vào boundary để bắt lỗi của action, hãy dùng useTransition, và tự thử cấu hình của mình bằng một lỗi cố ý.

Cách cài thấy được mọi trường hợp#

Mỗi loại lỗi React đi đâu, và cái gì báo được nó
Từ thí nghiệm ngày 26/10/2026
  1. Công cụ theo dõi lỗi khởi động trước app và lắng nghe window error và unhandledrejection. Phần này lo handler, timer, promise và transition độc lập.
  2. Đủ ba root callback, cái nào cũng báo lỗi. Phần này lo lỗi render boundary đã bắt, lỗi render không boundary nào bắt, và lỗi phục hồi được sau hydration.
  3. Boundary quanh những phần dễ hỏng của trang, để một biểu đồ hỏng chỉ hiện giao diện dự phòng thay vì làm trắng tất cả.
  4. Thông báo rõ ràng trong handler bị lỗi. Một try/catch hiện thông báo, kèm báo lỗi, để người dùng không phải bấm mãi một nút chết.

Belvador giúp ở đâu#

@belvador/react gồm các listener trên window của SDK trình duyệt, một ErrorBoundary báo lỗi kèm component stack, và reactErrorHandler() cho ba tuỳ chọn root của React 19. Hướng dẫn SDK React đặt cách cài cho React 18 và React 19 cạnh nhau, còn hướng dẫn SDK trình duyệt lo phần listener trên window. Trang React gom toàn bộ phần cài đặt vào một chỗ.

Đọc tiếp#

Andrew Babo

Nhà sáng lập

Thuộc bài hướng dẫn: Xử lý lỗi JavaScript trong ứng dụng do AI viết: lỗi thoát ra từ đâu

Mọi bài viết

Đọc tiếp trong tài liệu

Xem lỗi production của chính bạn như thế này

Belvador chạy trên máy chủ Hetzner của chính bạn: lỗi, cron, uptime và trace — dữ liệu vẫn là của bạn.