TypeScript dùng try, catch, finally và throw của JavaScript. Phần riêng của TypeScript nằm ở biến của catch: khi bật strict, nó có kiểu unknown, nên bạn phải kiểm tra thứ được ném ra trước khi dùng.
Phần lúc chạy (cách stack được gỡ ra, có những kiểu lỗi có sẵn nào) được trình bày trong try/catch trong JavaScript.
Vì sao biến của catch có kiểu unknown
JavaScript có thể ném ra bất cứ thứ gì: một Error, một chuỗi, một số, undefined, một đối tượng từ thư viện. TypeScript không thể biết là thứ nào, nên khi bật strict (cờ useUnknownInCatchVariables), biến này có kiểu unknown và bạn phải thu hẹp nó trước khi dùng:
index.ts(5,34): error TS18046: 'e' is of type 'unknown'.
Không có strict, e có kiểu any và cùng đoạn code đó biên dịch được, rồi hỏng lúc chạy khi một chuỗi bị ném ra: e.message là undefined, và bất cứ thứ gì sâu hơn, như e.message.length, sẽ ném ra TypeError. Bạn cũng không thể thoát bằng cách chú thích kiểu: catch (e: Error) gây lỗi TS1196 (Catch clause variable type annotation must be 'any' or 'unknown' if specified).
Thu hẹp kiểu của lỗi
instanceof Error bao quát mọi thứ xây dựng trên Error, gồm TypeError, SyntaxError, RangeError và các lớp con do bạn tự viết. Với những thứ còn lại, hãy chuyển giá trị thành chuỗi. Một hàm trợ giúp nhỏ giúp chỗ gọi gọn gàng:
Một catch chỉ xử lý một số lỗi thì nên ném lại phần còn lại: if (!(e instanceof ValidationError)) throw e;. Nuốt mất những lỗi chưa biết sẽ che giấu các bug thật.
Ném lỗi
throw nhận bất kỳ biểu thức nào, và TypeScript không giới hạn điều đó. Dù vậy, hãy ném đối tượng Error: chúng mang theo stack trace, và mọi phép kiểm tra instanceof Error trong codebase đều dựa vào điều này.
Một hàm luôn ném lỗi có kiểu trả về never, và TypeScript dùng điều đó để thu hẹp kiểu sau lời gọi:
Từ ES2022, Error nhận đối số thứ hai có cause, dùng để nối một lỗi cấp thấp vào lỗi bạn ném ra: throw new Error("could not load settings", { cause: e }). Nơi gọi có thể đọc err.cause (kiểu unknown).
Lớp lỗi tùy chỉnh
Một lớp con của Error giúp nơi gọi phân biệt các lỗi bằng instanceof và mang theo dữ liệu bổ sung. Hãy đặt name, vì giá trị kế thừa là "Error" và nó xuất hiện trong log cũng như trong String(err):
Kiểm tra lớp cụ thể nhất trước, vì một NotFoundError cũng là một HttpError. Từ khóa override ở đây là tùy chọn, trừ khi bật noImplicitOverride.
Các hướng dẫn cũ thêm Object.setPrototypeOf(this, new.target.prototype) vào mọi hàm khởi tạo lỗi. Điều đó cần thiết khi biên dịch lớp xuống hàm ES5, lúc instanceof bị hỏng với lớp con của Error. TypeScript 7 không còn hỗ trợ target: "es5" (nó báo TS5108: Option 'target=ES5' has been removed), và với ES2015 trở lên thì class ... extends Error gốc hoạt động bình thường, như ở trên.
Mẫu Result
TypeScript không theo dõi những lỗi mà một hàm có thể ném ra, nên không có gì nhắc nơi gọi xử lý chúng. Với những lỗi đã lường trước (đầu vào không hợp lệ, thiếu một bản ghi), trả về một giá trị cho biết "thành công hay thất bại" sẽ đưa lỗi vào hệ thống kiểu:
Đọc r.value trước khi kiểm tra r.ok là lỗi biên dịch, và đó chính là mục đích. Dùng ngoại lệ (exception) cho những điều bất ngờ (bug, kết nối bị đứt) và giá trị Result cho những kết quả mà nơi gọi phải tự quyết định.
Lỗi trong code bất đồng bộ
Một promise bị từ chối trở thành lỗi được ném ra tại await, nên try/catch bao quanh await hoạt động theo cùng cách, và biến lỗi lại có kiểu unknown. Khác biệt duy nhất là .catch() trên một promise: tham số của callback có kiểu any, không phải unknown, nên bạn tự chú thích kiểu (.catch((e: unknown) => ...)). Xem async/await để có ví dụ.
Lỗi thường gặp
- Đọc
e.messagemà không thu hẹp kiểu. Khi bậtstrictthì code không biên dịch được; không cóstrictthì hỏng khi thứ bị ném ra không phảiError. - Bắt mọi lỗi rồi chạy tiếp. Xử lý những lỗi bạn đã lường trước và ném lại phần còn lại.
- Quên đặt
namecho lỗi tùy chỉnh. Khi đó log ghiErrorcho tất cả. - Ném chuỗi.
throw "failed"không có stack trace và không qua được phép kiểm trainstanceof Error.
Câu hỏi thường gặp
Biến lỗi trong khối catch của TypeScript có kiểu gì?
unknown khi bật strict (cờ useUnknownInCatchVariables), ngược lại là any. JavaScript có thể ném ra bất kỳ giá trị nào, không chỉ đối tượng Error, nên TypeScript buộc bạn phải kiểm tra. Thu hẹp nó bằng if (e instanceof Error) trước khi đọc e.message.
Có thể khai báo kiểu Error cho biến của catch trong TypeScript không?
Không. catch (e: Error) gây lỗi TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified. Chỉ unknown và any được phép, vì không có gì đảm bảo thứ được ném ra là gì. Thay vào đó, hãy thu hẹp kiểu bên trong khối.
Làm sao để ném lỗi trong TypeScript?
Giống như trong JavaScript: throw new Error("message"), hoặc một instance của lớp con có sẵn hay tự viết như new RangeError(...). TypeScript cho phép ném bất kỳ giá trị nào, nhưng ném đối tượng Error giúp giữ lại stack trace và khiến phép kiểm tra instanceof Error hoạt động.
Làm sao để tạo lớp lỗi tùy chỉnh trong TypeScript?
Kế thừa Error, gọi super(message, options) và đặt name: class NotFoundError extends Error { name = "NotFoundError"; }. Các trường bổ sung đặt trong hàm khởi tạo. Với mọi target được hỗ trợ (ES2015 trở lên), instanceof NotFoundError hoạt động mà không cần sửa prototype.
TypeScript có checked exception hay mệnh đề throws không?
Không. Kiểu của một hàm không nói gì về những lỗi nó có thể ném ra, và trình biên dịch không bao giờ kiểm tra xem lỗi đã được xử lý chưa. Với những lỗi mà nơi gọi cần xử lý, hãy trả chúng về dưới dạng giá trị bằng kiểu Result (một discriminated union của thành công và thất bại) để hệ thống kiểu kiểm tra giúp bạn.