Xử lý Error trong JavaScript

Tram Ho

Khi lập trình, chúng ta thường sợ error. Nhưng bằng cách làm việc với nó, ta học được tại sao không nên làm cái này (và cái kia), cũng như làm thế nào để code ngon hơn.

Bài viết này chia thành 3 phần, đầu tiên là tổng quan về error. Sau đó chúng ta sẽ tập trung vào backend (Node.js + Express.js) và cuối cùng là cách xử lý error trong React.js.

I. JavaScript Errors và generic handling

throw new Error('something went wrong') – sẽ tạo một thể hiện của Error trong JavaScript và ngừng thực thi đoạn code, cho đến khi bạn làm gì đó với Error. Khi mới lập trình JavaScript, thường thì bạn sẽ không làm như vậy, mà chỉ bắt gặp nó khi xài thư viện (hoặc khi thực thi code), chẳng hạn ReferenceError: fs is not defined.

Error Object

Object Error có 2 thuộc tính để ta có thể sử dụng. Một là message, tức là đối số bạn truyền vào Error constructor, ví dụ new Error('This is the message'). Bạn có thể truy cập message thông qua thuộc tính messsage:

Thuộc tính thứ hai cực kỳ quan trọng chính là stack trace của Error. Bạn có thể truy xuất nó thông qua thuộc tính stack. Stack sẽ cho bạn lịch sử những file chịu trách nhiệm gây ra lỗi đó. Stack cũng chứa message và theo sau chính là những điểm xảy ra lỗi và file tương ứng.

Throwing và Handling Errors

Vì thể hiện của Error không có bất kỳ hiệu ứng, chẳng hạn new Error(‘…’) không làm bất cứ thứ gì. Khi Error throw n, nó sẽ trở nên thú vị hơn. Đoạn mã sẽ ngừng thực thi cho đến khi bạn xử lý nó ở đâu đó. Hãy nhơ là dù bạn throw lỗi thủ công hay là do thư viện hoặc runtime. Hãy xem cách chúng ta có thể làm việc với Error trong những kịch bản khác nhau như thế nào.

try ... catch
Đây là cách đơn giản nhất, nhưng thường bị bỏ quên khi xử lý lỗi – may là ngày nay nó được sử dụng thường xuyên hơn nhờ async / await. Nó cũng được dùng để bắt lỗi đồng bộ. Ví dụ:

Nếu chúng ta không đặt console.log(b) trong try ... catch, đoạn code sẽ ngừng thực thi.

… finally
Đôi lúc ta cần phải thực thi code dù bất cứ điều kiện nào xảy ra, lúc này bạn có thể dùng block thứ 3 là finally, chỉ cần thêm một dòng sau try ... catch.

Xử lý bất đồng bộ — Callbacks

Bất đồng bộ là chủ đề mà bạn luôn phải lưu ý khi làm việc với JavaScript. Khi bạn có một hàm bất đồng bộ, và một Error xảy ra trong hàm đó, đoạn mã sẽ tiếp tục thực thi, bởi vậy không có lỗi nào được bắn ra ngay. Khi xử lý hàm bất đồng bộ với callback, bạn thường nhận 2 đối số trong callback, thường sẽ như này:

Nếu có Error, tham số err sẽ tương đương với lỗi đó. Nếu không thì tham số này sẽ là undefined hoặc null.

Bất đồng bộ – Promises

Một cách tốt hơn để xử lý bất đồng bộ là dùng promises. Bên cạnh việc code dễ độc hơn, chúng ta cũng cải thiện việc xử lý lỗi. Không cần phải quan tâm quá nhiefu đến việc bắt chính xác Error, miễn là có block catch là được. Khi chain promise, mỗi một block catch sẽ bắt tất cả lỗi từ khi thực thi promise hoặc từ block catch cuối cùng. Lưu ý rằng promise mà không có catch sẽ không thể terminate code, nhưng bạn sẽ nhận một message khó đọc hơn như sau:

Bởi vậy hãy luôn thêm một block catch cho promise. Như sau:

try … catch — again

Với việc xuất hiện async / await trong JavaScript, ta trở lại với cách bắt lỗi cổ điển, với try ... catch ... finally, khá nhẹ nhàng:

II. Xử lý lỗi trong Server

Giờ chúng ta đã có những công cụ để làm việc với Error, hãy xem trong thực tế thì ta sẽ thực hiện như thế nào. Xử lý lỗi ở backend là một phần trong ứng dụng của bạn. Có nhiều cách tiếp cận với vấn đề này, tôi sẽ chỉ cho bạn cách tiếp cận với việc custom Error constructor và Error codes, mà ta có thể dể dàng truyền đến frontend hoặc API.

Ta sẽ dùng Express.js framework làm routing. Thử nghĩ cấu trúc mà ta muốn bắt lỗi một cách hiệu quả nhất. Ta muốn:

  1. Xử lý lỗi chung, chẳng hạn: Something went wrong, please try again or contact us.. Cách làm này không phải quá hay, nhưng ít nhất là nó thông báo cho user có gì đó không đúng, thay vì màn hình loading vô hạn.
  2. Xử lý lỗi cụ thể để đưa cho user thông tin chi tiết về lỗi và cách xử lý, chẳng hạn có một số thông tin đang thiếu, nội dung nhập vào đã có trong cơ sở dữ liệu, …

Building custom Error constructor

Chúng ta sẽ sử Error constructor và mở rộng nó. Kế thừa là phương án khá mạo hiểm trong JavaScript, nhưng lần này thì nó rất hữu ích. Tại sao chúng ta cần nó? Bởi vì ta vẫn muốn stack trace để debug hiệu quả hơn. Chúng ta chỉ cần thêm code, sau này sẽ truy xuất thông qua err.code, cũng như status để truyền đến frontend.

Xử lý routing

Như đã nói, chúng ta muốn một single point of truth để xử lý lỗi, nghĩa là mọi route đều có cách xử lý lỗi như nhau. Mặc định thì express không hỗ trợ vì route đã được đóng gói.

Để giải quyết vấn đề này, ta có thể cài đặt một route handler và định nghĩa logic như function thông thường. Bằng cách đó, khi function routing throw error, nó sẽ được trả lại route handler, rồi truyền đến frontend. Mỗi khi có lỗi xảy ra ở backend, chúng ta muốn truyền response đến frontend, giả sử là JSON API theo format sau:

Route handler trông như này:

Hãy thử xem file routing sẽ trông như nào:

Ở đây tôi không xử lý gì với request, tôi chỉ thêm một số kịch bản lỗi. Về cơ bản là bạn sẽ có một lỗi chưa xử lý, frontend sẽ nhận được:

hoặc bạn sẽ throw một CustomError:

sẽ trở thành

III. Hiển thị lỗi đến người dùng

Bước cuối cùng là quản lý lỗi ở phía frontend. Bạn muốn xử lý lỗi ở cả frontend cũng như backend. Đầu tiên hãy xem cách chúng ta hiển thị lỗi. Như đã nối thì tôi sẽ dùng React để minh họa.

Lưu Errors trong React state

Errors và message có thể thay đổi, bởi vậy bạn cần đặt chúng trong component state. Mặc định thì error sẽ được reset để lần đầu tiên user view sẽ không thấy lỗi.

Tiếp theo, ta cần phân biệt ra những kiểu Error khác nhau. Có 3 loại:

  1. Global Error, ví dụ generic error ở backend hoặc user không đăng nhập …
  2. Lỗi cụ thể từ backend, chẳng hạn password không đúng …
  3. Lỗi cụ thể từ frontend, chẳng hạn lỗi validation chưa nhập input

Global Errors

Thường thì tôi lưu những lỗi này ở component cha và render static UI.

Như bạn thấy, ta có error ở state. Chúng ta cũng có method để reset và thay đổi giá trị của error. Ta truyền giá trị và reset xuống component GlobalError, nó sẽ hiển thị và reset error khi người dùng click dấu x:

Ở dòng 5, ta không render vì không có error. Giờ bạn có thể sử dụng global error state bất cứ vị trí nào, chẳng hạn khi một request từ backend trả về với lỗi error: 'GENERIC':

Xử lý lỗi cụ thể

Tương tự global error, chúng ta có local error state trong component.

Error internationalisation với error code

Có thể bạn thắc mắc những error code như GENERIC dùng để làm gì. Khi ứng dụng lớn dần, có thể bạn sẽ phải hỗ trợ đa ngôn ngữ, lúc đó trả về message dựa theo ngôn ngữ của user sẽ đơn giản hơn khi match với error code.

Hy vọng rằng bạn đã có thể một cách nhìn mới về việc xử lý lỗi. console.error(err) đã trở thành quá khứ. Nó khá tiện khi debug nhưng lại mất thời gian khi build cho production, tốt nhất là nên dùng một thư viện logging.

Tham khảo: https://levelup.gitconnected.com/the-definite-guide-to-handling-errors-gracefully-in-javascript-58424d9c60e6

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo