Lỗi là đối tượng có kiểu
Khi JavaScript ném lỗi, nó không chỉ đưa bạn một chuỗi mà là một đối tượng. Đối tượng đó có một kiểu (hàm khởi tạo của nó) và vài thuộc tính chuẩn: name, message và stack.
err.name là một nhãn ngắn như "TypeError". err.message là phần mô tả dễ đọc. err instanceof TypeError cho bạn biết lớp cụ thể. Biết kiểu lỗi rất quan trọng: nó cho bạn biết vấn đề là gõ nhầm, giá trị sai, hay code không phân tích được.
Có bảy loại lỗi có sẵn. Ba loại bạn sẽ gặp liên tục, bốn loại thỉnh thoảng mới gặp.
SyntaxError: code không phân tích được
SyntaxError nghĩa là JavaScript thậm chí không đọc được code của bạn. Nó không hẳn là lỗi lúc chạy: engine thất bại ngay khi phân tích, trước khi bất kỳ dòng nào chạy. Bạn không thể try/catch một SyntaxError trong cùng tệp, vì cả tệp bị từ chối.
function greet(name {
return "hi, " + name;
}
// SyntaxError: Unexpected token '{'
Thiếu dấu ngoặc, thừa dấu phẩy, return nằm ngoài hàm: bất cứ thứ gì phá vỡ ngữ pháp đều ném ra lỗi này. Cách sửa luôn là chỉnh lại mã nguồn. Nơi duy nhất bạn có thể bắt SyntaxError là khi phân tích lúc chạy, như JSON.parse:
JSON.parse nhận một chuỗi lúc chạy, nên lỗi cú pháp của nó bắt được. Lỗi trong chính các tệp mã nguồn của bạn thì không.
ReferenceError: tên đó không tồn tại
ReferenceError xảy ra khi bạn tham chiếu tới một biến chưa được khai báo trong bất kỳ phạm vi nào mà đoạn code hiện tại nhìn thấy.
Chín mươi phần trăm trường hợp là do gõ nhầm (totl thay vì total). Mười phần trăm còn lại là do phạm vi: bạn đang dùng một thứ được khai báo ở hàm hoặc module khác.
Còn một nguyên nhân tinh vi hơn: temporal dead zone. Các khai báo let và const tồn tại từ đầu khối lệnh của chúng, nhưng bạn không thể truy cập chúng trước dòng khai báo:
x đã là một binding thật khi console.log(x) chạy, nhưng nó chưa được khởi tạo. Vì thế mới có lỗi tham chiếu. Cách sửa là chuyển dòng truy cập xuống dưới dòng khai báo.
TypeError: giá trị không đúng dạng
TypeError nghĩa là giá trị có tồn tại, nhưng không phải loại mà thao tác cần. Gọi một thứ không phải hàm, đọc thuộc tính của null hoặc undefined, gán lại cho một const: tất cả đều là TypeError.
"Cannot read properties of null (reading 'name')" có lẽ là thông báo lỗi phổ biến nhất trong JavaScript. Cách sửa là đảm bảo giá trị tồn tại, hoặc bảo vệ việc truy cập bằng optional chaining: user?.name.
Các dạng TypeError khác:
Gọi một con số, gán lại một const, gọi một phương thức không tồn tại: giá trị không đúng kiểu cho việc bạn yêu cầu.
RangeError: số nằm ngoài giới hạn
RangeError xảy ra khi một con số về mặt kỹ thuật là hợp lệ nhưng nằm ngoài phạm vi cho phép của một thao tác cụ thể.
Nguồn kinh điển là đệ quy vô hạn, làm tràn call stack:
"Maximum call stack size exceeded" gần như luôn có nghĩa là một hàm đang gọi chính nó mà không có điều kiện dừng, hoặc hai hàm đang gọi lẫn nhau thành vòng lặp.
URIError và EvalError: những loại hiếm gặp
URIError đến từ các hàm xử lý URI (encodeURI, decodeURIComponent và các hàm tương tự) khi chúng nhận đầu vào sai định dạng:
EvalError là di tích. Các engine JavaScript hiện đại không thực sự ném nó trong trường hợp nào cả; nó vẫn tồn tại như một hàm khởi tạo để tương thích ngược. Bạn có thể tự tạo một cái, nhưng sẽ không gặp nó ngoài thực tế.
Chuỗi kế thừa
Tất cả các loại này đều kế thừa từ lớp gốc Error. Nghĩa là err instanceof Error là true với bất kỳ loại nào, rất tiện trong một khối catch tổng quát:
Một khối catch bắt mọi thứ, kể cả những giá trị không phải lỗi nếu ai đó viết throw "oops". Nhánh cuối cùng đó rất quan trọng. Hãy luôn thu hẹp bằng instanceof trước khi coi một giá trị bắt được là đối tượng lỗi.
Chủ động tạo lỗi
Bạn có thể tự ném bất kỳ loại lỗi có sẵn nào khi code phát hiện ra vấn đề. Hãy chọn loại khớp với lỗi:
Chọn đúng loại cho lỗi không chỉ để cho đẹp: nó giúp nơi gọi viết logic catch có chủ đích thay vì phải phân tích thông báo lỗi.
Lớp lỗi tùy chỉnh
Khi các loại có sẵn không phù hợp, hãy kế thừa Error:
Hai điều cần nhớ: gọi super(message) để lớp gốc Error được thiết lập đúng, và đặt this.name để log hiển thị đúng nhãn. Các trường tùy chỉnh như field giúp nơi gọi phản ứng với từng kiểu lỗi cụ thể mà không cần phân tích chuỗi.
Tiếp theo: Console và DevTools
Biết các loại lỗi mới là một nửa chặng đường; nửa còn lại là đọc stack trace và xem xét trạng thái khi chương trình đang chạy. DevTools của trình duyệt (và trình gỡ lỗi của Node) biến "nó ném lỗi mà mình không biết tại sao" thành vài giây kiểm tra. Đó là bài tiếp theo.
Câu hỏi thường gặp
JavaScript có những loại lỗi có sẵn nào?
JavaScript có sẵn bảy loại: Error (lớp gốc), SyntaxError, ReferenceError, TypeError, RangeError, URIError và EvalError. Mỗi loại là một hàm khởi tạo tạo ra đối tượng lỗi có các thuộc tính name, message và stack. Bạn sẽ gặp SyntaxError, ReferenceError và TypeError nhiều nhất.
SyntaxError và TypeError khác nhau thế nào?
SyntaxError nghĩa là code không phải JavaScript hợp lệ: engine thậm chí không phân tích được nó. TypeError nghĩa là code được phân tích bình thường nhưng làm một việc không hợp lệ lúc chạy, như gọi một thứ không phải hàm hoặc đọc thuộc tính của null. Lỗi cú pháp chặn toàn bộ script; lỗi kiểu chỉ xảy ra khi dòng code sai được chạy.
Khi nào tôi gặp ReferenceError trong JavaScript?
Khi bạn dùng một tên chưa được khai báo, hoặc truy cập một biến let/const trong temporal dead zone của nó (trước khi dòng khai báo chạy). Gõ nhầm là nguyên nhân phổ biến nhất: consoel.log(x) ném ra ReferenceError: consoel is not defined. Hãy kiểm tra chính tả và phạm vi trước.
Tôi có thể tự tạo loại lỗi riêng không?
Có. Hãy kế thừa lớp Error có sẵn: class ValidationError extends Error { }. Đặt this.name trong hàm khởi tạo để log và các nhánh catch phân biệt được nó. Lớp lỗi tùy chỉnh rất đáng giá khi các lỗi khác nhau cần cách xử lý khác nhau.