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 Đề?

  1. 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.
  2. 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.
  3. 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ào getUserAge, 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()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!

Nguyễn Quang Tú
SUNTECH VIỆT NAM   Đăng ký để nhận thông báo mới nhất