Featured image of post Tối Ưu Hóa Việc Handle Error Cho Async Function Trong Javascript

Tối Ưu Hóa Việc Handle Error Cho Async Function Trong Javascript

Trong series bài về Clean Code mình đã đề cập đến việc tối ưu handle error cũng là 1 cách để khiến code của các bạn trở nên gọn gàng và dễ đọc hơn, trong tuần này khi review code của 1 member trong team mình tình cờ thấy được bạn ấy chưa tối ưu được handle error nên mình quyết định viết bài này. Mong là sẽ giúp một người có một góc nhìn tốt hơn về cách handle error trong Async function.

Đầu tiên, nếu mọi người muốn biết rõ hơn các khái niệm về Clean Code thì vui lòng đọc lại loạt bài về Clean Code II && Clean Code I giúp mình nhé!

Các sai lầm thường thấy khi handle error trong Javascript

Không sử dụng try/catch trong async function

Một trong những sai lầm phổ biến nhất khi làm việc với async function là không sử dụng try/catch để xử lý lỗi. Khi không có try/catch, bất kỳ lỗi nào xảy ra trong async function đều có thể làm gián đoạn toàn bộ luồng chương trình, gây khó khăn trong việc debug và xử lý sự cố.

1
2
3
4
5
6
7
8
9
async function fetchData(url) {
  const response = await fetch(url);
  const data = await response.json();
  return data;
}

fetchData('https://api.example.com/data').then((data) => {
  console.log(data);
});

Trong ví dụ trên, nếu fetch hoặc response.json() gặp lỗi, ứng dụng sẽ bị crash và bạn sẽ không biết nguyên nhân gây ra lỗi.

Xử lý lỗi một cách không nhất quán

Đây là trường hợp mình hay gặp nhất trong việc handle error. Nguyên nhân bởi vì mỗi người có mỗi style handle error khác nhau và do không có sự thống nhất từ trước hoặc các util function để handle error dẫn đến việc handle error không nhất quán giữa các function với nhau.
Việc xử lý lỗi không nhất quán giữa các async function có thể làm mã của bạn trở nên khó duy trì và khó đọc. Điều này có thể dẫn đến việc bỏ sót lỗi hoặc xử lý lỗi không đồng nhất, làm giảm hiệu quả của ứng dụng.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
async function getUserData(userId) {
  try {
    const response = await fetch(`/users/${userId}`);
    return await response.json();
  } catch (error) {
    console.error('Error fetching user data:', error);
  }
}

async function getProductData(productId) {
  const response = await fetch(`/products/${productId}`);
  if (!response.ok) {
    throw new Error('Error fetching product data');
  }
  return await response.json();
}

Trong ví dụ trên, getUserData sử dụng try/catch để xử lý lỗi, trong khi getProductData lại kiểm tra lỗi mà không dùng try/catch, dẫn đến sự không nhất quán trong cách xử lý lỗi.

Không log lỗi hoặc log không đủ chi tiết

Một số quên hoặc bỏ qua việc log lỗi, hoặc log lỗi không đủ chi tiết khiến cho việc debug và sửa lỗi trở nên khó khăn hơn nhiều. Nếu không log lỗi, bạn sẽ không có thông tin cần thiết để hiểu và khắc phục sự cố khi chúng xảy ra.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
async function fetchData(url) {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return data;
  } catch (error) {
    // Không log lỗi
    throw error;
  }
}

Hậu quả: Khi không có log, bạn sẽ mất rất nhiều thời gian để tìm ra lỗi xảy ra ở đâu và tại sao nó xảy ra. Điều này đặc biệt quan trọng trong môi trường sản xuất, nơi mà việc debug trực tiếp có thể không khả thi.

Không xử lý lỗi ở tất cả các vị trí có thể xảy ra lỗi

Sai lầm: Chỉ xử lý lỗi ở một vài vị trí trong code mà không xử lý ở tất cả các điểm có thể xảy ra lỗi. Điều này có thể dẫn đến các lỗi không mong muốn và khó kiểm soát.

1
2
3
4
5
6
7
8
9
async function processData(url) {
  try {
    const data = await fetchData(url);
  } catch (error) {
    console.error('Error fetching data:', error);
  }

  process(data); // Hàm process có thể gây lỗi nhưng không được xử lý
}

Hậu quả: Trong ví dụ trên, nếu hàm process gặp lỗi, lỗi sẽ không được xử lý vì nó không nằm trong try/catch. Điều này dẫn đến chương trình bị crash mà không có bất kỳ thông tin gì về lỗi.

Sử dụng try/catch nhưng không rethrow error cho các level cao hơn xử lý tiếp

Sai lầm: Handle lỗi bằng try/catch nhưng không rethrow để xử lý ở mức cao hơn trong code. Điều này có thể làm mất dấu lỗi và khó khăn trong việc xử lý tổng thể.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
async function fetchData(url) {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error fetching data:', error);
    return;
    // Không ném lỗi tiếp
  }
}

async function main() {
  try {
    const data = await fetchData('https://api.example.com/data');
    console.log(data);
  } catch (error) {
    console.error('An error occurred:', error);
  }
}

main();

Hậu quả: Trong ví dụ này, lỗi xảy ra trong fetchData sẽ không được truyền lên hàm main, khiến hàm main không thể xử lý lỗi một cách tổng thể. Điều này dẫn đến việc bỏ sót lỗi và chương trình hoạt động không đúng cách.

Ví dụ về việc handle error chưa tốt mà mình gặp phải

Dưới đây là một ví dụ về cách xử lý lỗi chưa tốt, dẫn đến việc khó maintain và khó đọc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function request(data) {
  return new Promise((resovle, reject) => {
    data === 'correct' ? resovle(data) : reject('error happen');
  });
}

async function getData() {
  try {
    const response1 = await request('correct');
  } catch (error) {
    //handle error
    console.error('Error in request 1', error);
  }

  try {
    const response2 = await request('failed 1');
  } catch (error) {
    //handle error
    console.error('Error in request 2', error);
  }

  try {
    const response3 = await request('failed 2');
  } catch (error) {
    //handle error
    console.error('Error in request 3', error);
  }
}

getData();

Trong ví dụ này, vì muốn khi error xảy ra không ảnh hưởng tới các request khác nên người viết đã dùng try catch ở tất cả những điểm gọi request, việc này khiến cho code trở nên cồng kềnh và khó đọc, đặc biệt đối với những fucntion lớn
Chúng ta có thể đơn giản sửa lại đoạn code trên thông qua việc sử dụng catch callback của Promise để khiến code trở nên đơn giản dễ nhìn hơn

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function request(data) {
  return new Promise((resovle, reject) => {
    data === 'correct' ? resovle(data) : reject('error happen');
  });
}

async function getData() {
  const response1 = await request('correct').catch(
    (error) => console.error('errror: ', error)
    //handle error
  );

  const response2 = await request('failed 1').catch(
    (error) => console.error('Error in request 2: ', error)
    //handle error
  );

  const response3 = await request('failed 2').catch(
    (error) => console.error('Error in request 2: ', error)
    //handle error
  );
}

getData();

Hoặc chúng ta có thể sử dụng 1 utilise function để handleRequest giúp việc handle error trở nên đơn giản và nhất quán hơn:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function request(data) {
  return new Promise((resovle, reject) => {
    data === 'correct' ? resovle(data) : reject('error happen');
  });
}

async function getData() {
  const response1 = await handlerRequest('correct');

  const response2 = await handlerRequest('failed 1');

  const response3 = await handlerRequest('failed 2');
}

const handlerRequest = (data) => {
  return request(data)
    .then((response) => {
      console.log('response: ', response);
      return response;
    })
    .catch((error) => {
      console.error('Error in request: ', error);
    });
};

getData();

Bây giờ, function getData() của chúng ta đã trở nên gọn gàng và nhất quán hơn rất nhiều, Điều này giúp code trở nên rõ ràng hơn và dễ duy trì hơn.

Tổng kết

“CODE KHÔNG BAO GIỜ BIẾT NÓI DỐI, LẬP TRÌNH VIÊN THÌ ĐÔI KHI”

Việc xử lý lỗi trong async functionss là một yếu tố quan trọng trong việc tối ưu code và khiến code của chúng ta trở nên dễ maintain và khắc phục khi xảy ra sự cố. Những lỗi không được xử lý đúng cách có thể dẫn đến sự cố không mong muốn, làm gián đoạn trải nghiệm người dùng và gây khó khăn trong việc bảo trì code.

Hãy luôn nhớ rằng, lỗi không phải là kẻ thù của lập trình viên, mà là cơ hội để bạn làm cho ứng dụng của mình tốt hơn. Hãy tận dụng các kỹ thuật và công cụ đã được chia sẻ trong bài viết này để nâng cao chất lượng code của bạn.

HAPPY CODING!

Built with Hugo
Theme Stack designed by Jimmy