Xử Lý Lỗi Trong TypeScript: Đừng throw, Hãy Dùng neverthrow
Trong TypeScript, nhiều hàm có thể ném lỗi (throw
), nhưng TypeScript lại mặc định xử lý chúng như thể chúng luôn thành công. Điều này có thể dẫn đến các lỗi không mong muốn và kiểu dữ liệu không chính xác, gây khó khăn trong việc kiểm soát chương trình.
Hãy cùng khám phá vấn đề này qua một ví dụ và xem cách neverthrow
giúp chúng ta xử lý lỗi một cách an toàn và rõ ràng hơn.
Vấn Đề: Lỗi Ẩn (Hidden Exceptions
)
Giả sử chúng ta có một hàm lấy tuổi của người dùng từ cơ sở dữ liệu. Nếu userId
không hợp lệ, hàm sẽ ném lỗi:
function getUserAge(userId: number): number {
if (userId < 0) throw new Error("Invalid user ID");
return 25; // Trả về một số tuổi giả định
}
const age = getUserAge(-1); // Có thể ném lỗi, nhưng TypeScript vẫn coi kiểu dữ liệu là number!
console.log("User age:", age);
Tại Sao Đây Là Vấn Đề?
- Kiểu dữ liệu không chính xác: TypeScript giả định
getUserAge()
luôn trả vềnumber
, dù thực tế nó có thể ném lỗi. - Crash không mong muốn: Khi
userId
không hợp lệ, chương trình sẽ bị dừng ngay lập tức thay vì tiếp tục chạy bình thường. - Không an toàn về kiểu dữ liệu: Chữ ký hàm không thể hiện rõ rằng có thể xảy ra lỗi.
Khi di chuột vàogetUserAge
, TypeScript vẫn báo kiểu trả về lànumber
, gây hiểu lầm cho lập trình viên.
Giải Pháp: Sử Dụng neverthrow
Thay vì ném lỗi, chúng ta có thể sử dụng thư viện neverthrow
để trả về một kết quả rõ ràng hơn.
Cách Cải Tiến:
import { ok, err, Result } from "neverthrow";
function getUserAge(userId: number): Result<number, string> {
if (userId < 0) return err("Invalid user ID"); // Trả về lỗi rõ ràng
return ok(25); // Trả về kết quả thành công
}
const ageResult = getUserAge(-1);
Kiểu Dữ Liệu Mới:
Result<number, string>
Ok<number>
: Trả về số tuổi khi thành công.Err<string>
: Trả về chuỗi lỗi khi thất bại.
Tại Sao Cách Này Tốt Hơn?
✅ Không còn lỗi ẩn: Không ném lỗi, giúp chương trình không bị crash bất ngờ.
✅ Lỗi được xác định rõ ràng: Thay vì một ngoại lệ (exception
), chúng ta có một kiểu trả về có thể dự đoán.
✅ Xử lý lỗi an toàn: Chúng ta bắt buộc phải xử lý cả hai trường hợp (thành công và thất bại), tránh bỏ sót lỗi.
Cách Xử Lý Kết Quả
Bây giờ, thay vì kiểm tra lỗi bằng try/catch
, chúng ta có thể sử dụng các phương thức của neverthrow
:
Cách 1: Dùng if
để xử lý lỗi
if (ageResult.isErr()) {
console.error("Error:", ageResult.error); // Kiểu lỗi là "Invalid user ID"
} else {
console.log("User age:", ageResult.value); // Kiểu trả về là number
}
Cách 2: Sử Dụng map()
và mapErr()
Thay vì dùng if
, ta có thể xử lý ngắn gọn hơn bằng cách chain
các kết quả:
ageResult
.map(age => console.log("User age:", age)) // Chạy nếu thành công
.mapErr(error => console.error("Error:", error)); // Chạy nếu thất bại
Điều này giúp mã ngắn gọn hơn và dễ đọc hơn.
Kết Luận
Sử dụng neverthrow
giúp bạn tránh khỏi những lỗi ẩn và làm cho mã nguồn của bạn dễ dự đoán hơn. Thay vì ném lỗi (throw
), chúng ta có thể trả về lỗi một cách rõ ràng, giúp TypeScript suy ra chính xác kiểu dữ liệu cho cả trường hợp thành công và thất bại.
✅ Không còn lỗi ẩn, không còn crash bất ngờ
✅ Dữ liệu an toàn, dễ đọc và dễ bảo trì hơn
✅ Loại bỏ kiểu dữ liệu sai lệch trong TypeScript
Hãy thử áp dụng neverthrow
vào dự án của bạn ngay hôm nay để tăng độ an toàn và chất lượng mã nguồn!