Một số phương pháp xử lý lỗi (error handling) trong Node.js

Một số phương pháp xử lý lỗi (error handling) trong Node.js

Tin ngắn hàng ngày dành cho bạn
  • Đây là bài viết thứ 366, có nghĩa là mình đã duy trì được việc mỗi ngày một bài trong chuyên mục Threads 1 năm rồi đấy mọi người. Tuy rằng không phải ngày nào cũng viết vì nhiều hôm mình bận, quên thì hôm sau là lên bài bù, mục đích là để cam kết với độc giả, ấy vậy mà quay đi quay lại đã một năm trôi qua rồi. Nhanh thật 😃

    À mai, kia, ngày kìa nữa mình đi du lịch nên chắc không lên bài cho mọi người được. Về rồi mình lên sau nhé 😅. Cảm ơn!

    » Xem thêm
  • Hơn 1 tuần nay mình không đăng bài, không phải không có gì để viết mà đang tìm cách để phân phối nội dung có giá trị hơn trong thời đại AI đang bùng nổ mạnh mẽ như thế này.

    Như từ hồi đầu năm đã chia sẻ, số lượng người truy cập vào trang blog của mình đang dần ít đi. Khi xem thống kê, lượng người dùng trong 6 tháng đầu năm 2025 đã giảm 30% so với cùng kì năm ngoái, 15% so với 6 tháng cuối năm 2024. Như vậy một sự thật là người dùng đang rời bỏ dần đi. Nguyên nhân do đâu?

    Mình nghĩ lý do lớn nhất là thói quen của người dùng đã thay đổi. Họ tìm thấy blog chủ yếu qua các công cụ tìm kiếm, trong đó lớn nhất là Google. Gần 1/2 số lượng người dùng quay trở lại blog mà không cần thông qua bước tìm kiếm. Đó là một tín hiệu đáng mừng nhưng vẫn không đủ để tăng lượng người dùng mới. Chưa kể giờ đây, Google đã ra mắt tính năng AI Search Labs - tức là AI hiển thị luôn nội dung tổng hợp khi người dùng tìm kiếm, điều đó càng khiến cho khả năng người dùng truy cập vào trang web thấp hơn. Một điều thú vị là khi Search Labs được giới thiệu, thì các bài viết bằng tiếng Anh đã soán ngôi trong bảng xếp hạng truy cập nhiều nhất.

    Một bài viết của mình thường rất dài, có khi lên đến cả 2000 chữ. Mà để viết ra được một bài như thế tốn nhiều thời gian. Nhiều bài viết ra chẳng có ai đọc là điều bình thường. Mình biết và chấp nhận vì không phải ai cũng gặp phải vấn đề đang nói đến. Viết đối với mình như một cách để rèn luyện sự kiên nhẫn và cả tư duy. Viết ra mà giúp được cả ai đó là một điều tuyệt vời.

    Vậy nên mình đang nghĩ sẽ tập trung vào nội dung ngắn và trung bình để viết được nhiều hơn. Nội dung dài chỉ khi muốn viết chi tiết hoặc đi sâu về một chủ đề nào đó. Nên là đang tìm cách thiết kế lại trang blog. Mọi người cùng chờ nha 😄

    » Xem thêm
  • CloudFlare đã giới thiệu tính năng pay per crawl để tính phí cho mỗi lần AI "cào" dữ liệu trên trang web của bạn. Là sao ta 🤔?

    Mục đích của SEO là giúp các công cụ tìm kiếm nhìn thấy trang web. Khi người dùng tìm kiếm nội dung mà có liên quan thì nó hiển thị trang web của bạn ra kết quả tìm kiếm. Điều này gần như là đôi bên cùng có lợi khi Google giúp nhiều người biết đến trang web hơn, còn Google thì được nhiều người dùng hơn.

    Bây giờ cuộc chơi với các AI Agents thì lại khác. AI Agents phải chủ động đi tìm kiếm nguồn thông tin và tiện thể "cào" luôn dữ liệu của bạn về, rồi xào nấu hay làm gì đó mà chúng ta cũng chẳng thể biết được. Vậy đây gần như là cuộc chơi chỉ mang lại lợi ích cho 1 bên 🤔!?

    Nước đi của CloudFlare là bắt AI Agents phải trả tiền cho mỗi lần lấy dữ liệu từ trang web của bạn. Nếu không trả tiền thì tôi không cho ông đọc dữ liệu của tôi. Kiểu vậy. Hãy chờ thêm một thời gian nữa xem sao 🤓.

    » Xem thêm

Vấn đề

Lỗi là một vấn đề tồn tại song song với việc phát triển ứng dụng, tôi hay nói vui là chừng nào còn code thì còn "bug". Lỗi cũng có lỗi "this" lỗi "that", có lỗi chúng ta hoàn toàn nhận biết được chúng sẽ xảy ra trong tương lai, bên cạnh đó là những lỗi trời ơi đất hỡi mà chúng ta hoàn toàn không biết khả năng xuất hiện của chúng.

Lỗi gây ra những phiền toái, đôi khi gây ra những hậu quả nghiêm trọng. Chính vì thế xử lý lỗi luôn là vấn đề quan trọng trong lập trình. Bài viết hôm nay tôi xin trình bày một số phương pháp xử lý lỗi một cách "duyên dáng" trong môi trường Node.js.

Các phương pháp

Sử dụng async/await hoặc promise để xử lý lỗi ở các hàm không đồng bộ

Tránh sử dụng callback để rơi vào callback hell khiến mã của bạn trở nên lồng nhau và gây rối mắt, khó khăn cho bảo trì.

Ví dụ một trường hợp sử dụng callback để xử lý lỗi:

getData(someParameter, function(err_a, a) {
    if(err_a!== null) {
        getMoreDataA(a, function(err_b, b) {
            if(err_b !== null) {
                getMoreDataB(b, function(err_c, c) {
                    getMoreDataC(c, function(err_d, d) {
                        if(err_d !== null ) {
                            // do something
                        }
                    })
                });
            }
        });
    }
});

Thay vào đó hãy sử dụng promise để xử lý lỗi một cách "duyên dáng" hơn:

return getData(someParameter)
    .then(getMoreDataA)
    .then(getMoreDataB)
    ...
    .catch(err => handle(err));

Tuy nhiên promise có gây khó khăn trong khi debug, hãy sử dụng cú pháp "thanh lịch" của async/await kết hợp với try/catch:

try {
    const a = await getData(someParameter);
    const b = await getData(someParameter);
} catch(err) {
    handle(err);
}

Trả ra một đối tượng Error thay vì bất kì đối tượng có kiểu nào khác khi muốn báo lỗi.

Vì tính dễ dãi của JS chúng ta có thể "throw" ra tuỳ ý một đối tượng nào để báo lỗi, nó có thể là một số, một chuỗi hay một object nhưng đừng nên làm như thế. Hãy throw ra một Error để đảm bảo tính đồng nhất mã của bạn và với các thư viện, hơn nữa Error còn lưu giữ thông tin quan trọng như StackTrace là vị trí gây ra lỗi.

/ / đừng làm thế này
if (!condition) {
    throw ('điều kiện không hợp lệ');
}

// mà hãy làm thế này
if (!condition) {
    throw new Error('điều kiện không hợp lệ');
}

// hay thậm chí bạn có thể làm tốt hơn bằng cách tạo ra một đối tượng lỗi
// mang thêm nhiều thông tin hữu ích khác
function MyError(name, description, ...args) {
    Error.call(this);
    Error.captureStackTrace(this);
    this.name = name;
    this.description = description;
    ...
};

MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

if (!condition) {
    throw new MyError('CONDITION_NOT_VALID', 'điều kiện không hợp lệ');
}

Xử lý lỗi tập trung

Tạo ra một hoặc nhiều hàm sẵn sàng nhận vào một đối tượng lỗi sau đó thì phân tích hay phân phát lỗi đến các nơi khác, tránh xử lý riêng lẻ khiến cho mã của bạn trở nên khó kiểm soát.

Tưởng tượng bạn sẽ làm gì khi có lỗi xảy ra? Log ra console, gửi chúng đến các dịch vụ theo dõi (tracking) hay logging, ghi ra file, kiểm tra điều kiện và phân loại lỗi... rất nhiều thứ cần làm với một lỗi được bắn ra vì thế hãy tập hợp chúng lại trong một hoặc nhiều hàm chuyên để xử lý lỗi.

Thận trọng với lỗi unhandledRejection

Đây là một lỗi rất khó chịu trong Node.js, nó có thể khiến cho ứng dụng của bạn bị treo và không thể tiếp tục xử lý bất kì yêu cầu nào được nữa. Lỗi này xảy ra khi bạn xử lý Promise không đúng cách, cụ thể là không có hàm xử lý lỗi của reject trong Promise.

UserModel.findByPk(1).then((user) => {
  if(!user)
      throw new Error('user not found');
});

Để bắt được lỗi này, chúng ta cần sử dụng process.on.

process.on('unhandledRejection', (reason, p) => {
  throw reason;
});

process.on('uncaughtException', (error) => {
  errorManagement.handler.handleError(error);
  if (!errorManagement.handler.isTrustedError(error))
    process.exit(1);
});

process.on thường được tạo ngay index.js nơi mà ứng dụng của bạn khởi động đầu tiên để nó có thể "lắng nghe" bất kì tín hiệu nào từ việc xử lý Promise không đúng cách. Để từ đó có phương án xử lý thích hợp tránh máy chủ bị treo.

Đừng bao giờ tin dữ liệu đầu vào, luôn xác thực chúng.

Chúng ta cung cấp những endpoint POST, PUT... để cho phép client truyền dữ liệu lên, có một điều là dữ liệu không thể đảm bảo luôn luôn đúng. Vì thế cách an toàn nhất là luôn validate dữ liệu nhận được, ngăn chặn hành vi vô tình hay cố ý gửi sai dữ liệu mong muốn có thể gây ra lỗi hay cố tình phá hoại hệ thống.

Có nhiều thư viện rất tốt giúp chúng ta làm điều này như joi hay là ajv.

const schema = Joi.object({
  username: Joi.string()
    .alphanum()
    .min(3)
    .max(30)
    .required(),

  password: Joi.string()
    .pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),
});

let body = { username: '2coffee', password: '2coffee' };
schema.validate(body); // -> { value: { username: '2coffee', password: '2coffee' } }

let body = {};
schema.validate(body); // -> { value: {}, error: '"username" is required' }

Sử dụng trình ghi log chuyên nghiệp

Ghi log là một cách có thể theo dõi và giám sát những lỗi đã xảy ra trong quá khứ để có thể tra cứu lại bất kì lúc nào. Tôi có một bài viết riêng nói về việc ghi log trong ứng dụng Node.js, bạn đọc xem thêm tại Logging ứng dụng viết bằng node.js qua 3 cấp độ.

Hãy viết Unit test

Unit test là một trong những phương pháp giúp bạn phát hiện ra lỗi sớm nhất trong quá trình phát triển. Tuy mất nhiều thời gian để viết Unit test nhưng nó rất đáng giá để đầu tư thời gian của bạn. Một khi Unit test hoạt động tốt nó sẽ tiết kiệm cho bạn rất nhiều thời gian và chi phí sau này đấy.

"Thoát" ứng dụng khi có thể

Trong trường hợp đối diện với những lỗi đặc thù trước mắt chưa có cách nào chữa trị hoặc bắt được lỗi nhưng khó có thể khôi phục trạng thái ứng dụng thì cách xử lý tạm thời là hãy khiến cho ứng dụng bị crash và nhờ các công cụ DevOps khởi động lại. Đây có thể không phải là cách hay nhưng việc khởi động lại ứng dụng của bạn sẽ khôi phục lại được trạng thái trước khi xảy ra lỗi.

Sử dụng AMP

Application Performance Management (APM) được dùng để giám sát và quản lý hiệu suất cũng như theo dõi tính khả dụng của ứng dụng. APM cố gắng phát hiện và chuẩn đoán các vấn đề về hiệu suất ứng dụng và kịp thời thông báo cho bạn biết khi có vấn đề gì xảy ra.

Hiện tại có khá nhiều sản phẩm, dịch vụ APM trên thị trường từ Open Source, miễn phí đến trả phí. Tính năng cũng rất phong phú từ đơn giản đến phức tạp, theo dõi bằng việc "ping" hoặc tích hợp sâu vào theo dõi hệ thống phức tạp.

Hãy bắt đầu với một dịch vụ APM đơn giản như uptimerobot.com. Nó theo dõi thời gian uptime ứng dụng bằng cách gửi một yêu cầu sau mỗi 5 phút và chờ phản hồi thành công hay thất bại.

uptimerobot.com

Newrelic là một công cụ theo dõi toàn diện hơn vì nó theo dõi được nhiều thứ hơn như ngăn xếp lỗi chi tiết, thời gian phản hồi, nút thắt cổ chai và thống kê chi tiết...

newrelic

Tổng kết

Trên đây là một số phương pháp xử lý lỗi đối với ứng dụng viết bằng Node.js mà tôi đề xuất. Có thể ngoài những cách trên còn có nhiều phương pháp khác mà tôi chưa biết, nếu bạn phát hiện ra còn thiếu xin hãy để lại dưới phần bình luận.

Cao cấp
Hello

Bí mật ngăn xếp của Blog

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (1)

Nội dung bình luận...
Avatar
Jess Vanes2 năm trước

Hướng dẫn dùng mấy tool apm đi ạ

Trả lời
Avatar
Xuân Hoài Tống2 năm trước

@gif [ISOckXUybVfQ4] Chắc là sẽ hơi lâu ạ