01/07/2026
📖 ÁM ẢNH MANG TÊN "CALLBACK HELL" VÀ HÀNH TRÌNH TÌM LẠI BÌNH YÊN VỚI ASYNC/AWAIT
Năm đầu tiên sau khi học lập trình, tôi luôn tự hào mình là đứa viết code "sạch". Thẳng hàng, ngay ngắn, từ trên xuống dưới, cứ như một cuốn sách giáo khoa.
Cho đến khi tôi va phải Xử lý bất đồng bộ (Asynchronous) trong JavaScript. Đó cũng là lúc cuốn sách giáo khoa của tôi biến thành một cái "kim tự tháp" đổ nát mang tên: Callback Hell.
Hồi đó, tôi được giao một tính năng nghe qua thì rất đơn giản:
1. Đọc file cấu hình từ hệ thống để lấy URL.
2. Dùng URL đó gọi API lấy thông tin user.
3. Có thông tin user rồi thì tiếp tục gọi một API khác để lấy danh sách bài viết.
4. Cuối cùng là lọc dữ liệu và hiển thị lên màn hình.
Vì JavaScript chạy trên cơ chế Single Thread (đơn luồng) – nghĩa là tại một thời điểm nó chỉ xử lý một việc – nên để tránh việc trình duyệt bị "đóng băng" khi chờ đợi các tác vụ tốn thời gian như đọc file hay gọi API, JS buộc phải ném các tác vụ đó vào hàng đợi và xử lý dưới dạng bất đồng bộ. Và công cụ sơ khai nhất để làm việc này chính là Callback function.
Tôi bắt đầu cắm đầu vào code. Hàm 1 gọi hàm 2 thông qua callback, hàm 2 nhận kết quả lại lồng tiếp hàm 3, hàm 3 lồng hàm 4...
Chỉ sau khoảng 4 tầng lồng nhau, đoạn code của tôi bắt đầu "dạt" dần về phía bên phải màn hình. Nhìn từ xa, nó không khác gì một cái hình tam giác chĩa mũi nhọn ra vô cực.
Cơn ác mộng thực sự ập đến khi hệ thống báo lỗi. Tìm xem lỗi phát sinh ở tầng callback nào là một cực hình. Tôi phải căng mắt ra đếm từng dấu đóng mở ngoặc nhọn }), }), }). Chỉ cần sửa một logic nhỏ hoặc đổi tên biến, cả hệ thống lập tức sụp đổ như quân bài Domino. Lúc đó, tôi bất lực nhìn màn hình và tự hỏi: "Chẳng lẽ ngôn ngữ phổ biến nhất thế giới lại có cách vận hành ngu ngốc và tra tấn người dùng đến vậy sao?"
May mắn thay, JavaScript không dừng lại ở đó.
Bước ngoặt đến khi tôi biết đến Promises ở ES6. Nó giống như một lời cứu rỗi. Thay vì lồng code vào nhau, tôi có thể "xâu chuỗi" các tác vụ bằng .then(), .then(), .catch(). Code đã bắt đầu phẳng ra, dễ đọc hơn, việc bắt lỗi (error handling) cũng gom về một mối ở hàm .catch() cuối cùng thay vì phải if (err) ở từng tầng như trước.
Nhưng "chân ái" thực sự giúp tôi tìm lại sự bình yên tuyệt đối chính là sự xuất hiện của Async/Await (ES7).
Tôi vẫn nhớ cảm xúc lần đầu tiên refactor (tối ưu) lại đống Callback Hell ngày xưa bằng Async/Await. Toàn bộ cái "kim tự tháp" dạt phải biến mất hoàn toàn. Thay vào đó là một đoạn code thẳng tắp, viết bất đồng bộ nhưng đọc mượt mà như đồng bộ
Bản chất bên dưới của JavaScript vẫn là Single Thread, vẫn là cơ chế Event Loop dịch chuyển liên tục, nhưng async/await đã khoác lên nó một lớp áo "cú pháp" (Syntactic Sugar) tuyệt vời. Nó bắt bộ não của lập trình viên không còn phải "nhảy cóc" tư duy theo các hàm callback lồng chéo, mà có thể tập trung hoàn toàn vào luồng nghiệp vụ thực tế.
Hành trình từ Callback Hell tiến lên Async/Await đã dạy cho tôi một bài học xương máu: Đừng chỉ học cách viết code cho máy chạy, hãy học cách viết code sao cho con người có thể đọc, hiểu và bảo trì nó một cách dễ dàng.
JavaScript có thể có những điểm kỳ quặc ở thuở sơ khai, nhưng tư duy xử lý bất đồng bộ chuẩn chỉnh mới là thứ phân biệt giữa một Newbie viết code theo bản năng và một Developer thực thụ biết làm chủ ngôn ngữ.
👉 Nếu bạn cũng đang phân vân trên con đường làm chủ tư duy JavaScript, đây là thứ giúp tôi đi tiếp: https://bit.ly/3Qa3Vxw