Bên cạnh xử lý lỗi, tạo ra lỗi bằng đối tượng Error cũng không kém phần quan trọng

  • 0

  • 0

  • 0

Bên cạnh xử lý lỗi, tạo ra lỗi bằng đối tượng Error cũng không kém phần quan trọng

Tạo ra lỗi bằng đối tượng Error

Vấn đề

Cách đây khá lâu tôi có một bài viết nói về Một số phương pháp xử lý lỗi (error handling) trong Node.js, nội dung trong bài xoay quanh việc làm thế nào để bắt được lỗi và xử lý chúng một cách dễ dàng. Trong bài viết này, tạm thời bỏ qua những phương pháp đó, chúng ta hãy đi sâu vào phân tích việc "đẩy" ra lỗi và làm sao để "bắt" được chúng.

Tôi cá là có nhiều người, trong đó có cả tôi từng đẩy ra một lỗi trông giống như thế này:

function login(username, password) {
  // code
  if (password != hash_password) {
    throw new Error("Password is incorrect");
  }
// code
}

Sau đó "bắt" lỗi:

try {
  login("admin", "123456");
} catch(e) {
  console.log(e.message);
  // logic handle
}

Chẳng có gì nhiều để bàn đến cách xử lý lỗi ở trên cả, khi "đẩy" ra một Error, nó sẽ mang theo rất nhiều thông tin hữu ích gây ra lỗi như vị trí (stack trace) và thông tin lỗi (message). Vì thế cho nên e ở trong catch(e) giúp ta dễ dàng truy ra được nguồn gốc lỗi để từ đó xử lý sao cho hợp lý.

Tôi từng gặp trường hợp đẩy ra một lỗi không đúng cách, thay vì new Error, lại đẩy ra một chuỗi hay một đối tượng. Giống như là:

throw 'Password is incorrect';

Với cách này, chúng ta vẫn có thể try catch. Tuy nhiên, e lúc này chỉ đơn giản là một chuỗi hoặc một đối tượng mà bạn đã đẩy ra trước đó, nó không chứa những thông tin quan trọng như stack trace và message như của Error. Chính vì thế, bất cứ khi nào đẩy ra một lỗi, hãy biến nó thành một instance của Error.

Error chỉ nhận vào tham số là một chuỗi, nó cũng chính là nội dung củae.message. Nếu ta cố tình truyền vào nhiều hơn một tham số hoặc tham số là một thứ gì đó khác string như object thì sẽ gây ra một hiện tượng giống như dưới đây:

throw new Error({ "name": "2coffee" });

VM400:1 Uncaught Error: [object Object]
    at <anonymous>:1:7

Sở dĩ tôi nêu ra vấn đề này vì có nhiều trường hợp, chúng ta cần thêm nhiều thông tin được vào trong một lỗi để tiện cho việc xử lý logic nào đó. Giả dụ như bên cạnh message, tôi cần thêm uuid là id của bản ghi gây ra lỗi, detail chứa một mô tả chi tiết hơn, code để chỉ định mã lỗi được định nghĩa trong hệ thống… thì với Error, hoàn toàn là không được.

Vậy có cách nào làm được điều này không?

Errors Class

Error là một đối tượng bị "đẩy" ra khi xảy ra lỗi trong thời gian chạy (runtime error). Error cũng có thể được sử dụng để làm đối tượng cơ sở cho các lỗi do người dùng xác định. Hay nói một cách dân dã là có thể tạo ra một Class kế thừa Error để tạo ra một đối tượng lỗi cho riêng mình.

JavaScript cung cấp một số đối tượng lỗi "tùy chỉnh" khác dựa trên Error mà có thể bạn đã từng gặp rất nhiều rồi như:

Để xem danh sách đầy đủ Error types, bạn đọc tham khảo tại Error types - Mozilla.

Điểm chung là chúng dựa trên Error nên mang đầy đủ thuộc tính quan trọng của Error. Ngoài ra, các lỗi này còn mang một sự minh bạch trong mã của bạn. Ví dụ khi đẩy ra một lỗi RangeError chúng ta có thể biết lỗi là do một giá trị nào đó nằm ngoài giá trị cho phép, thay vì throw new Error('The argument must be between 1 and 10') một cách chung chung.

Để phân biệt các đối tượng lỗi, chỉ cần thông qua các câu lệnh điều kiện như if...else, switch...case

if (err instanceof RangeError) {
  // handle RangeError
} else if (err instanceof ReferenceError) {
  // handle TypeError
}
...

Các đối tượng lỗi tùy chỉnh này cũng tương tự như Error, chỉ khác mỗi name. Do đó nếu cần một lỗi "khác biệt" hơn, hãy chuyển sang phần tiếp theo.

Custom Errors Class

Chúng ta có thể tự xác định các loại lỗi của riêng mình bằng cách kế thừa từ Error. Sau đó, đẩy ra lỗi bằng cách throw MyError và sử dụng instanceof MyError để kiểm tra loại lỗi trong catch. Điều này dẫn đến các đoạn mã xử lý lỗi sạch sẽ và nhất quán hơn.

Cú pháp đơn giản nhất để tạo ra một MyError lỗi tùy chỉnh:

class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
  }
}

MyError lúc này được gán name bằng constructor.name cũng chính là cái tên MyError của class. Tuy vậy, nó chưa có nhiều khác biệt gì so với Error thông thường, chỉ khác mỗi name. Chúng ta cần thêm một vài thuộc tính như code là mã lỗi và statusCode để định nghĩa cho HTTP response status codes.

class MyError extends Error {
  code;
  statusCode;

  constructor(message, code, statusCode) {
    super(message);
    this.name = this.constructor.name;
    this.code = code;
    this.statusCode = statusCode;
  }
}

Để đẩy ra lỗi, sử dụng cú pháp throw MyError:

try {
  throw new MyError("My error message", 123, 404);
} catch (err) {
  console.log(err.name, err.message, err.code, err.statusCode);
  // MyError My error message 123 404
}

Dựa vào tính năng này, có thể tạo ra nhiều lỗi riêng cho mình để phục vụ cho mục đích xử lý. Ví dụ, tạo ra class ApplicationError, DatabaseError, ValidateError… có các tính năng tương tương với lỗi hệ thống, lỗi cơ sở dữ liệu, lỗi xác thực dữ liệu… để từ đó ẩn hoặc hiện thông báo lỗi cho người dùng.

Tổng kết

Error là một đối tượng để xử lý lỗi trong chương trình viết bằng JavaScript. Cách tốt nhất để đẩy ra một lỗi là đẩy ra một Error thay vì chuỗi hoặc một đối tượng khác. JavaScript định nghĩa sẵn một vài "kiểu" lỗi dựa trên Error như ReferenceError, SyntaxError, RangeError… Ngoài ra, có thể tự tạo kiểu lỗi cho riêng mình bằng cách kế thừa từ Error.

Tài liệu tham khảo:

Bạn thấy bài viết này có ích?
  • Không

  • Bình thường

Bình luận
DMCA.com Protection Status