Bạn có một dashboard đẹp: vài ô số liệu và một widget phức tạp. Widget đó gặp dữ liệu bất thường và ném lỗi khi render. Kết quả mặc định: không chỉ widget đó, mà cả màn hình sập trắng với "Rất tiếc, đã xảy ra lỗi" — vì lỗi lan ngược lên tận gốc cây component. Người dùng mất luôn cả những ô số liệu vốn chẳng liên quan. OWL có cách chặn đám cháy đó ở đúng một ô: error boundary bằng hook onError.
onError bắt lỗi từ component con
onError((err) => {...}) là một hook đăng ký trong setup(). Nó bắt lỗi phát sinh từ các component con (khi render hoặc trong lifecycle như onWillStart, onMounted). Khi bắt được, thay vì để lỗi lan lên, bạn đặt một cờ trong state và cho template hiện một giao diện thay thế (fallback):

Hình 1: KhungAnToan là một error boundary. onError bắt lỗi từ con (<ThanhPhanLoi/> đặt trong slot), đặt state.loi. Template: có lỗi thì hiện fallback (t-if="state.loi"), không thì render con (t-slot="default"). Boundary chỉ bọc phần có nguy cơ; ba ô số liệu nằm ngoài nó nên không bị ảnh hưởng.
Thấy tận mắt: một ô cháy, phần còn lại nguyên vẹn
Mình cố tình cho ThanhPhanLoi đọc thuộc tính của null khi render (this.duLieu.gia_tri với duLieu = null) — một lỗi rất hay gặp khi dữ liệu chưa kịp tải. Bọc nó trong KhungAnToan, đặt cạnh ba ô số liệu:

Hình 2: Kết quả thật. Widget lỗi bị chặn trong hộp đỏ "Ô này gặp sự cố, phần còn lại vẫn hoạt động" — không sập gì hết. Ba ô số liệu (4, 385, 1) render bình thường vì chúng nằm ngoài boundary. So với mặc định (cả trang trắng), khác biệt là một trải nghiệm hỏng cục bộ có thể chịu được thay vì mất trắng. Console cũng ghi lại lỗi đã bắt để lập trình viên còn lần ra.
Vài điều cần biết cho đúng
- onError chỉ bắt lỗi từ component CON khi render/lifecycle. Nó không bắt lỗi trong một
Promisechưaawait, mộtsetTimeout, hay một event handler async — những lỗi đó xảy ra ngoài chu trình render, boundary không thấy. Xử lý chúng bằngtry/catchtại chỗ. - Vẫn phải log lỗi. Bắt để UI đẹp không có nghĩa là giấu lỗi. Ghi
console(hoặc gửi về hệ thống theo dõi lỗi) để còn sửa — như ví dụ trên cóconsole.warn. - Đặt boundary ở đúng độ sâu. Bọc quá rộng (cả trang) thì một lỗi nhỏ vẫn ẩn cả trang; bọc quá hẹp thì viết nhiều. Bọc quanh những mảnh độc lập, dễ hỏng (widget lấy dữ liệu ngoài, biểu đồ, phần do người dùng cấu hình).
- Odoo lõi đã dùng pattern này. Web client bọc các phần chính bằng error handler để một action hỏng không giết cả giao diện — bạn đang dùng đúng cơ chế đó cho phần của mình.
Ba ý mang về
- Một lỗi khi render một component có thể kéo sập cả cây giao diện.
onError((err) => {...})là error boundary: bắt lỗi từ component con, chuyển sang hiện fallback, giữ phần còn lại của UI sống. - Mẫu viết:
setup()gọionErrorđặtstate.loi; templatet-if="state.loi"hiện thông báo thay thế,t-elserender con (thường quat-slot). Bọc quanh những mảnh độc lập, dễ hỏng — đừng bọc cả trang. - onError chỉ bắt lỗi render/lifecycle của con, KHÔNG bắt lỗi async rời rạc (
Promisechưa await,setTimeout, handler async) — dùngtry/catchcho chúng. Và vẫn phải log lỗi để còn sửa.
Ta đã viết khá nhiều JS cho web client. Nhưng làm sao biết nó đúng mà không phải mở trình duyệt bấm thử mỗi lần? Phần sau nói về kiểm thử JS bằng QUnit/Hoot — bộ khung test frontend của Odoo, cách viết một test cho component và chạy nó.