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
  • Cảm ơn threads.net của nhà Meta vì nó là nguồn cảm hứng cho mình tạo ra chuyên mục này trên blog. Ban đầu hơi nghi ngờ về việc liệu tạo ra các bài viết ngắn như thế này có thu hút được người dùng, có ai ngày qua ngày quay trở lại đọc không, hay tất cả chỉ như dã tràng xe cát? Như mình đã nói rất nhiều là làm ra một tính năng không khó, nhưng vận hành nó làm sao cho hiệu quả mới là điều cần phải bận tâm.

    Giờ đây thời gian đã chứng minh tất cả. Chuyên mục Bài viết ngắn luôn đứng trong tốp 5 trang có lượt truy cập nhiều nhất trong ngày/tuần/tháng. Điều đó có nghĩa bạn đọc đã có thói quen quay trở lại nhiều hơn. Tại sao mình lại khẳng định như thế? Vì chuyên mục này gần như không hề được SEO trên các công cụ tìm kiếm như Google.

    Lại kể về thời xa xưa một chút. Thời gian đầu mình rất chịu khó đăng bài trên threads.net với hy vọng thu hút được nhiều người theo dõi, để từ đó khéo léo giới thiệu họ trở thành người dùng blog của mình. Nhưng càng về sau càng thấy "đuối" vì thuật toán của Threads ngày càng không phù hợp với định hướng của mình. Hay nói cách khác là nội dung tạo ra không ăn khách.

    Ví dụ các bài viết của mình thường mang khuynh hướng chia sẻ thông tin, tin tức, hoặc kinh nghiệm cá nhân rút ra sau khi học hoặc làm một cái gì đó. Dường như những bài viết như vậy không được đánh giá cao và thường bị chôn vùi chỉ sau hơn... 100 lượt xem. Hmm... Liệu vấn đề có phải là do mình? Biết thế sao không chịu thay đổi nội dung theo hướng phù hợp hơn với nền tảng?

    Mình đã quan sát Threads, các nội dung dễ lan toả nhất là có yếu tố gây tranh cãi hoặc một định kiến về vấn đề gì đó, đôi khi chỉ đơn giản là phát biểu "ngây ngô" một vấn đề gì đó mà họ biết chắc chắn có tương tác. Mà mình thì gần như là không hề thích định hướng người dùng theo nội dung kiểu này. Mọi người có thể bảo mình bảo thủ, mình chấp nhận. Mỗi người có định hướng nội dung và khán giả khác nhau, lựa chọn nằm ở họ.

    Thế là từ đó mình chủ yếu viết trên này. Chỉ thi thoảng có phát hiện hay lắm thì mới lên Threads "khoe". Ở đây hàng ngày vẫn có người vào đọc, dù cho bạn là ai thì mình tin chắc rằng các bạn nhận ra được thông điệp mà mình muốn truyền tải thông qua mỗi bài viết. Ít nhất chúng ta có chung một định hướng về nội dung. Đôi khi điều sợ nhất không phải là viết ra không ai đọc, mà là họ đọc xong rồi lãng quên trong phút chốc. Số lượng là quan trọng, nhưng chất lượng mới là thứ mang chúng ta lại gần nhau hơn.

    Cảm ơn tất cả 🤓

    » Xem thêm
  • Zed chắc là cộng đồng những nhà phát triển chịu khó lắng nghe người dùng nhất quả đất. Mới đây họ thêm tuỳ chọn để tắt tất tần tật tính năng AI có trong Zed. Trong khi nhiều bên khác đang muốn tích hợp sâu hơn và làm nhiều hơn với AI Agent. Quả là một nước đi táo bạo 🤔

    You Can Now Disable All AI Features in Zed

    » Xem thêm
  • Hôm nay mình đã cố gắng đi hẳn 8k bước trong một phiên để đo lường cho các bạn thấy. Quả là không ngoài dự đoán khi thời gian đi lên đến hơn 1 giờ và quãng đường ~6km 🤓

    À vài hôm nữa là hết tháng, tức là cũng tròn 1 tháng mình bắt đầu thói quen đi bộ mỗi ngày với mục tiêu 8k bước. Để đầu tháng sau mình tổng kết lại xem thế nào luôn ha.

    » 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

5 bài học sâu sắc

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? 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 ạ