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
  • Từ lâu rồi suy nghĩ làm thế nào để tăng sự hiện diện thương hiệu, cũng như người dùng cho blog. Nghĩ đi nghĩ lại thì chỉ có cách chia sẻ lên mạng xã hội hoặc trông chờ họ tìm kiếm, cho đến khi...

    In cái áo này được cái tắc đường khỏi phải lăn tăn, càng đông càng vui vì hàng trăm con mắt nhìn thấy cơ mà 🤓

    (Có tác dụng thật nha 🤭)

    » Xem thêm
  • Một vòng của sự phát triển nhiều dự án khá là thú vị. Tóm tắt lại trong 3 bước: Thấy một cái gì đó phức tạp -> Làm cho nó đơn giản đi -> Thêm thắt tính năng cho đến khi nó phức tạp... -> Quay trở lại vòng lặp mới.

    Tại sao lại như vậy? Để mình lấy 2 ví dụ cho các bạn thấy.

    Markdown ra đời với mục tiêu tạo ra một định dạng văn bản thô "dễ viết, dễ đọc, dễ dàng chuyển thành một dạng gì đó như HTML". Vì thời đó chẳng ai đủ kiên nhẫn mà vừa ngồi viết vừa thêm định dạng cho văn bản hiển thị ở trên web như thế nào. Ấy vậy mà giờ đây người ta đang "nhồi nhét" hoặc tạo ra các biến thể dựa trên markdown để bổ sung thêm nhiều định dạng mới đến mức... chẳng nhớ nổi hết cú pháp.

    React cũng là một ví dụ. Từ thời PHP, việc khát khao tạo ra một cái gì đó tách biệt hẳn giao diện người dùng và phần xử lý logic chính của ứng dụng thành 2 phần riêng biệt cho dễ đọc, dễ viết. Kết quả là các thư viện UI/UX phát triển rất mạnh mẽ, mang lại khả năng tương tác với người dùng rất tốt, còn phần logic ứng dụng thì nằm ở một máy chủ riêng biệt. Bộ đôi Front-end, Back-end cũng từ đấy mà thịnh hành, không thể thiếu anh bồi bàn REST API. Ấy vậy mà giờ đây React trông cũng không khác biệt gì so với PHP là mấy, kéo theo là cả Vue, Svelte... lại cùng quy tất cả về một mối.

    Cơ mà không phải vòng lặp là xấu, ngược lại vòng lặp này mang tính tiến hoá nhiều hơn là "cải lùi". Nhiều khi lại tạo ra được cái hay hơi cái cũ thế là người ta lại dựa trên cái hay đó để tiếp tục lặp. Nói cách khác là chắc lọc tinh hoa từng tí một tí một á 😁

    » Xem thêm
  • Song song với các dự án chính thức thì thi thoảng mình vẫn thấy các dự án "bên lề" nhằm tối ưu hoặc cải tiến ngôn ngữ theo khía cạnh nào đó. Ví dụ nature-lang/nature là một dự án hướng tới cải tiến Go, mang lại một số thay đổi nhằm giúp cho việc sử dụng Go trở nên thân thiện hơn.

    Nhìn lại mới thấy hao hao JavaScript 😆

    » 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 ạ