- vừa được xem lúc

Giải Quyết Callback Hell: 3 Cấp Độ Quản Lý Bất Đồng Bộ trong JavaScript

0 0 9

Người đăng: Lâm Phú Cường

Theo Viblo Asia

Khi làm việc với các tác vụ bất đồng bộ trong JavaScript, lập trình viên sẽ thường xuyên phải đối mặt với vấn đề xử lý tuần tự các hành động không đồng bộ. Điều này có thể dẫn đến Callback Hell, làm mã trở nên phức tạp và khó bảo trì. Bài viết này sẽ giúp bạn hiểu rõ về các cấp độ xử lý bất đồng bộ trong JavaScript: từ Callbacks, đến Promises, và cuối cùng là Async/Await.

Level 1: Callbacks - Sự Khởi Đầu của Callback Hell

Callback Hell là tình trạng mã bị lồng nhau quá sâu khi phải thực hiện nhiều hành động bất đồng bộ tuần tự. Đây là cấp độ cơ bản nhất khi làm việc với các tác vụ không đồng bộ, và cũng là cấp độ dễ mắc lỗi nhất.

Ví dụ về Callback Hell:

javascript
Copy code
doStep1(function(result1) { doStep2(result1, function(result2) { doStep3(result2, function(result3) { doStep4(result3, function(result4) { console.log('Hoàn thành tất cả các bước với kết quả:', result4); }); }); });
}); 

Trong ví dụ này, mỗi hàm callback được lồng bên trong hàm khác, dẫn đến việc mã trở nên khó đọc, khó bảo trì và dễ mắc lỗi nếu thêm nhiều bước xử lý hơn.

Vấn đề của Callback Hell:

  • Mã lồng nhau quá sâu: Làm giảm tính dễ đọc và dễ hiểu của mã.
  • Khó bảo trì: Mỗi lần thêm hoặc chỉnh sửa logic sẽ rất dễ tạo ra lỗi.
  • Khó xử lý lỗi: Việc quản lý lỗi trong nhiều callback lồng nhau trở nên phức tạp.

Level 2: Promises - Giải Quyết Callback Hell

Nếu bạn là một lập trình viên từng thấy mình "lạc" trong những đoạn mã callback lồng nhau như một mê cung, thì Promises chính là "siêu anh hùng" đến giải cứu bạn khỏi cảnh "callback hell". Promises cho phép bạn quản lý các tác vụ bất đồng bộ một cách gọn gàng hơn, giống như bạn đang xếp hàng đợi đến lượt, thay vì nhảy vào một mớ lộn xộn không ai biết mình phải làm gì trước.

Hãy tưởng tượng bạn đang gọi món pizza:

  • Bạn gọi điện cho tiệm pizza (doStep1).
  • Sau đó bạn muốn nhận tin nhắn xác nhận rằng pizza đang được nướng (doStep2).
  • Tiếp theo là bạn đợi shipper giao pizza đến cửa (doStep3).
  • Cuối cùng là ăn pizza và tận hưởng thành quả (doStep4).

Nếu không có Promises, bạn sẽ phải lồng các yêu cầu như dưới đây:

gọiPizza(function(xácNhậnGọi) { nướngPizza(xácNhậnGọi, function(xácNhậnNướng) { giaoPizza(xácNhậnNướng, function(xácNhậnGiao) { ănPizza(xácNhậnGiao, function() { console.log('Pizza đã đến, ăn thôi!'); }); }); });
}); 

Trông như một chuỗi dài vô tận ? Đó chính là callback hell. Nhưng với Promises, bạn có thể quản lý mọi thứ như sau:

gọiPizza() .then((xácNhậnGọi) => { return nướngPizza(xácNhậnGọi); // Đợi pizza được nướng }) .then((xácNhậnNướng) => { return giaoPizza(xácNhậnNướng); // Đợi shipper giao pizza }) .then((xácNhậnGiao) => { return ănPizza(xácNhậnGiao); // Đến giờ ăn rồi! }) .then(() => { console.log('Pizza đã đến, ăn thôi!'); }) .catch((error) => { console.error('Có gì đó không ổn với đơn hàng pizza:', error); }); 

Bây giờ thì bạn đã xếp hàng lần lượt rồi! Mỗi bước trong chuỗi .then() sẽ chờ bước trước hoàn thành, giống như bạn là khách hàng đang đợi đến lượt mình trong hàng pizza.


Lợi ích của Promises:

  • Không cần lo "trễ hẹn": Ta sẽ biết chính xác khi nào pizza được giao đến (chứ không phải ngồi "chờ trong mòn mỏi" mà không biết kết quả ra sao).
  • Dễ xử lý lỗi hơn: Nếu có gì sai sót (pizza cháy khét, hay shipper đi lạc), Ta chỉ cần dùng .catch() để xử lý lỗi.

Level 3: Async/Await - Viết Mã Bất Đồng Bộ "Thư Giãn" như Mã Đồng Bộ

Nếu Promises là siêu anh hùng giúp lập trình viên thoát khỏi callback hell, thì Async/Await là người anh hùng bình tĩnh hơn, giúp ta viết mã bất đồng bộ mà trông như mã đồng bộ quen thuộc.

Async/Await chỉ như là một lớp "trang điểm" cho Promises, nhưng khiến mã của bạn trông gọn gàng hơn nhiều và dễ bảo trì. Thay vì phải dùng .then() liên tục, ta chỉ cần thêm từ khóa await trước mỗi hành động bất đồng bộ để chờ nó hoàn thành.

Hãy quay lại với ví dụ về việc gọi pizza:

Callback Hell

Lúc đầu, ta đã phải lồng quá nhiều callback khiến mã trở nên rối rắm:

gọiPizza(function(xácNhậnGọi) { nướngPizza(xácNhậnGọi, function(xácNhậnNướng) { giaoPizza(xácNhậnNướng, function(xácNhậnGiao) { ănPizza(xácNhậnGiao, function() { console.log('Pizza đã đến, ăn thôi!'); }); }); });
}); 

Promises

Sau đó, ta tiến hoá lên sử dụng Promises để làm cho mọi thứ dễ đọc hơn:

gọiPizza() .then((xácNhậnGọi) => { return nướngPizza(xácNhậnGọi); }) .then((xácNhậnNướng) => { return giaoPizza(xácNhậnNướng); }) .then((xácNhậnGiao) => { console.log('Pizza đã đến, ăn thôi!'); }) .catch((error) => { console.error('Lỗi xảy ra:', error); }); 

Async/Await

Nhưng bây giờ với Async/Await, mã gần giống như đang lập trình tuần tự:

async function làmPizza() { try { const xácNhậnGọi = await gọiPizza(); // Gọi pizza xong thì chờ kết quả const xácNhậnNướng = await nướngPizza(xácNhậnGọi); // Chờ pizza được nướng const xácNhậnGiao = await giaoPizza(xácNhậnNướng); // Chờ shipper giao pizza console.log('Pizza đã đến, ăn thôi!'); } catch (error) { console.error('Có sự cố trong quá trình làm pizza:', error); }
} làmPizza(); 

Lợi ích của Async/Await:

  1. Nhìn mã như đồng bộ: Mặc dù các tác vụ bên trong là bất đồng bộ, nhưng nhờ từ khóa await, mã trông như đang thực hiện các hành động tuần tự, giúp dễ hiểu hơn.
  2. Xử lý lỗi dễ dàng hơn: Ta có thể dùng try/catch để bắt lỗi ngay tại nơi chúng xảy ra, thay vì phải dùng .catch() ở cuối chuỗi.
  3. Dễ bảo trì: Khi cần thêm hoặc sửa logic, lập trình viên chỉ cần làm điều đó như thể đang viết mã đồng bộ, không cần phải lo về việc lồng callback hay nối .then().

Kết Luận

Trong JavaScript, việc quản lý các tác vụ bất đồng bộ có thể được thực xem như có ba cấp độ:

  1. Callbacks (Level 1): Là phương pháp cơ bản nhưng dễ dẫn đến callback hell khi các hàm lồng nhau quá nhiều, khiến mã trở nên phức tạp và khó bảo trì.
  2. Promises (Level 2): Giải quyết vấn đề callback hell bằng cách cho phép nối chuỗi các hành động bất đồng bộ một cách tuần tự hơn với .then(), giúp mã dễ đọc hơn và quản lý lỗi dễ dàng hơn.
  3. Async/Await (Level 3): Là cú pháp hiện đại giúp viết mã bất đồng bộ như mã đồng bộ, làm cho mã dễ hiểu hơn và dễ bảo trì. Async/Await còn giúp việc xử lý lỗi trở nên gọn gàng với try/catch.

Bằng cách sử dụng các phương pháp phù hợp, ta có thể làm cho mã JavaScript bất đồng bộ của mình trở nên gọn gàng, dễ bảo trì và tránh được những vấn đề thường gặp như callback hell,...

Bình luận

Bài viết tương tự

- vừa được xem lúc

Imports và Exports trong JavaScript ES6

. Giới thiệu. ES6 cung cấp cho chúng ta import (nhập), export (xuất) các functions, biến từ module này sang module khác và sử dụng nó trong các file khác.

0 0 113

- vừa được xem lúc

Phân biệt kiểu biến var, let, và const trong JavaScript

1. Giới Thiệu. ES6 (viết tắt của ECMAScript 6) là một tập hợp các kỹ thuật nâng cao của Javascript. ECMAScript do hiệp hội các nhà sản xuất máy tính Châu Âu đề xuất làm tiêu chuẩn của ngôn ngữ Javascript.

0 0 63

- vừa được xem lúc

Hãy sử dụng ESLint cho dự án của bạn!

. Bài viết gốc: https://manhhomienbienthuy.bitbucket.io/2018/May/20/we-should-use-eslint-in-project.html (đã xin phép tác giả ).

0 0 76

- vừa được xem lúc

Object Literals nâng cao với ES6

Trong bài viết này chúng ta xem xét những gì có thể xảy ra với các Object Literals trong JavaScript, đặc biệt là theo các bản cập nhật ECMAScript gần đây. Khả năng tạo các đối tượng JavaScript sử dụng ký hiệu chữ rất mạnh mẽ.

0 0 33

- vừa được xem lúc

Một số mẹo vặt "hay ho" của ES6 có thể bạn chưa biết - Phần 4

Xin chào, ở 3 bài trước của series "Một số mẹo vặt "hay ho" của ES6", mình đã chia sẻ 1 số tips/tricks nhỏ với ES6, hy vọng ít nhiều nó sẽ có ích với các bạn khi áp dụng vào thực tế. Hôm nay, xin mời các bạn theo dõi phần 4 của series này.

0 0 46

- vừa được xem lúc

Tìm hiểu về JavaScript Module

Chắc hẳn ai trong chúng ta cũng đã từng sử dụng nhiều công cụ như là webpack, rollup, grunt, browserify,...; sử dụng những cú pháp module quen thuộc của CommonJS, AMD hay là ES6, nhưng có lẽ là chưa thực sự nhiều người đã nắm rõ về quá trình hình thành và mục đích tại sao chúng ta có những công cụ n

0 0 44