⚡ JavaScript Async Programming

Lập Trình Bất Đồng Bộ - Callbacks, Promises, Async/Await

1. 🚀 Giới Thiệu Lập Trình Bất Đồng Bộ (Async Programming)

🤔 Tại sao cần Lập Trình Bất Đồng Bộ?

💡 Khái niệm cơ bản

Lập trình bất đồng bộ (Asynchronous Programming) là phương pháp lập trình cho phép chương trình thực hiện nhiều tác vụ cùng lúc mà không cần chờ đợi tác vụ trước hoàn thành.

  • Đồng bộ (Synchronous): Thực hiện từng tác vụ một cách tuần tự, phải chờ tác vụ trước hoàn thành
  • Bất đồng bộ (Asynchronous): Có thể khởi chạy nhiều tác vụ cùng lúc và xử lý kết quả khi chúng hoàn thành

⚠️ Vấn đề với Lập Trình Đồng Bộ (Synchronous Code)

JavaScript chạy trên luồng đơn (single thread), nghĩa là chỉ có thể thực hiện một tác vụ tại một thời điểm. Khi có tác vụ tốn thời gian như:

  • Gọi API (API call): Lấy dữ liệu từ server
  • Đọc file (File reading): Đọc file từ hệ thống
  • Truy vấn database: Lấy dữ liệu từ cơ sở dữ liệu
  • Tính toán phức tạp: Xử lý dữ liệu lớn

Thì sẽ chặn (block) toàn bộ ứng dụng, khiến giao diện người dùng bị đơ, không thể tương tác!

So sánh Đồng bộ vs Bất đồng bộ - Ví dụ thực tế
JavaScript
// ❌ LẬP TRÌNH ĐỒNG BỘ (SYNCHRONOUS) - Chặn luồng chính
console.log('🚀 Bắt đầu tải dữ liệu');

// Giả lập tác vụ tốn thời gian - ví dụ gọi API
function layDuLieuTuServer() {
    // Đây là tác vụ ĐỒNG BỘ - sẽ chặn toàn bộ chương trình
    let startTime = Date.now();
    // Vòng lặp để mô phỏng delay 3 giây
    while (Date.now() - startTime < 3000) {
        // Không làm gì cả, chỉ chờ đợi - CHẶN LUỒNG CHÍNH!
    }
    return { data: 'Dữ liệu từ server', id: 123 };
}

let duLieu = layDuLieuTuServer(); // ⏰ CHẶN 3 GIÂY Ở ĐÂY!
console.log('📦 Nhận được:', duLieu);
console.log('✅ Kết thúc');

// Trong 3 giây này, người dùng KHÔNG THỂ tương tác với trang web!
// Nút bấm không hoạt động, scroll không được, nhập liệu bị lag...

// ✅ LẬP TRÌNH BẤT ĐỒNG BỘ (ASYNCHRONOUS) - Không chặn luồng
console.log('🚀 Bắt đầu tải dữ liệu');

// setTimeout là hàm BẤT ĐỒNG BỘ - không chặn luồng chính
setTimeout(() => {
    // Code này sẽ chạy SAU 3 giây, nhưng không chặn luồng chính
    console.log('📦 Nhận được: { data: "Dữ liệu từ server", id: 123 }');
    console.log('🎯 Tác vụ bất đồng bộ hoàn thành');
}, 3000); // 3000ms = 3 giây

// Code này chạy NGAY LẬP TỨC, không cần chờ setTimeout
console.log('✅ Tiếp tục thực hiện các tác vụ khác');
console.log('💡 Người dùng vẫn có thể tương tác với trang web');

// Kết quả in ra:
// 🚀 Bắt đầu tải dữ liệu
// ✅ Tiếp tục thực hiện các tác vụ khác  
// 💡 Người dùng vẫn có thể tương tác với trang web
// (sau 3 giây) 📦 Nhận được: { data: "Dữ liệu từ server", id: 123 }
// (sau 3 giây) 🎯 Tác vụ bất đồng bộ hoàn thành

🔍 Hiểu rõ hơn về cơ chế hoạt động

🧠 Cách JavaScript xử lý bất đồng bộ

JavaScript sử dụng Event Loop (Vòng lặp sự kiện) để xử lý các tác vụ bất đồng bộ:

  1. Call Stack (Ngăn xếp gọi hàm): Nơi thực thi code đồng bộ
  2. Web APIs: Xử lý các tác vụ bất đồng bộ (setTimeout, fetch, DOM events)
  3. Callback Queue (Hàng đợi callback): Nơi chứa các hàm callback chờ được thực thi
  4. Event Loop: Kiểm tra và chuyển callback từ Queue vào Call Stack khi Call Stack trống

📈 So sánh thời gian thực thi

1
Đồng bộ (Synchronous): Tác vụ 1 → Chờ 3 giây → Tác vụ 2 → Chờ 2 giây → Tác vụ 3
Tổng thời gian: 3 + 2 = 5+ giây. Người dùng bị chặn suốt 5 giây!
2
Bất đồng bộ (Asynchronous): Khởi chạy tất cả tác vụ cùng lúc → Xử lý kết quả khi sẵn sàng
Tổng thời gian: ~0.1 giây để khởi chạy. Người dùng vẫn tương tác được!

🎯 Ứng dụng thực tế của Lập trình Bất đồng bộ

✅ Khi nào cần sử dụng Async Programming?

  • Gọi API: Lấy dữ liệu từ server (thời tiết, tin tức, thông tin user...)
  • Upload/Download file: Tải lên hoặc tải xuống file lớn
  • Truy vấn Database: Lấy dữ liệu từ cơ sở dữ liệu
  • Xử lý hình ảnh/video: Resize, crop, convert format
  • Gửi email: Gửi thông báo, newsletter
  • Timer/Scheduler: Thực hiện tác vụ theo lịch trình
  • Animation: Tạo hiệu ứng chuyển động mượt mà

🎯 Bài Tập Ôn Tập - Phần Giới Thiệu

📝 Bài Tập 1: Phân biệt Đồng bộ vs Bất đồng bộ

Yêu cầu: Tạo hai hàm để so sánh sự khác biệt giữa xử lý đồng bộ và bất đồng bộ

Mục tiêu: Hiểu rõ sự khác biệt và tác động đến performance

Bài Tập 1: So sánh Sync vs Async
JavaScript
// TODO: Hoàn thành các hàm sau

// Bài Tập 1a: Tạo hàm xử lý ĐỒNG BỘ
function xuLyDongBo() {
    console.log('⏰ Bắt đầu xử lý đồng bộ');
    
    // TODO: Tạo một vòng lặp chặn luồng trong 2 giây
    // Gợi ý: Sử dụng while loop với Date.now()
    
    console.log('✅ Hoàn thành xử lý đồng bộ');
}

// Bài Tập 1b: Tạo hàm xử lý BẤT ĐỒNG BỘ  
function xuLyBatDongBo() {
    console.log('⚡ Bắt đầu xử lý bất đồng bộ');
    
    // TODO: Sử dụng setTimeout để mô phỏng tác vụ 2 giây
    // Không chặn luồng chính
    
    console.log('🚀 Tiếp tục các tác vụ khác');
}

// Bài Tập 1c: Test và so sánh
function testSoSanh() {
    console.log('🧪 Test 1: Xử lý đồng bộ');
    const startSync = Date.now();
    xuLyDongBo();
    const endSync = Date.now();
    console.log(`⏱️ Thời gian chặn: ${endSync - startSync}ms\n`);
    
    console.log('🧪 Test 2: Xử lý bất đồng bộ');
    const startAsync = Date.now();
    xuLyBatDongBo();
    const endAsync = Date.now();
    console.log(`⚡ Thời gian không chặn: ${endAsync - startAsync}ms`);
}

// TODO: Gọi hàm testSoSanh() và quan sát kết quả
// testSoSanh();

📝 Bài Tập 2: Mô phỏng tình huống thực tế

Yêu cầu: Tạo ứng dụng đơn giản mô phỏng việc tải dữ liệu từ nhiều nguồn

Mục tiêu: Thấy được lợi ích của async trong ứng dụng thực tế

Bài Tập 2: Mô phỏng Loading Dashboard
JavaScript
// Bài Tập 2: Mô phỏng tải dữ liệu Dashboard

// TODO: Hoàn thành các hàm mô phỏng API calls
function taiThongTinNguoiDung() {
    console.log('📱 Đang tải thông tin người dùng...');
    // TODO: Sử dụng setTimeout 1.5 giây, sau đó log thành công
}

function taiThongKeBanHang() {
    console.log('📊 Đang tải thống kê bán hàng...');
    // TODO: Sử dụng setTimeout 2 giây, sau đó log thành công
}

function taiDanhSachSanPham() {
    console.log('🛍️ Đang tải danh sách sản phẩm...');
    // TODO: Sử dụng setTimeout 1 giây, sau đó log thành công
}

// Bài Tập 2a: Cách tải TUẦN TỰ (sequential)
function taiDashboardTuanTu() {
    console.log('🔄 === LOADING TUẦN TỰ ===');
    const startTime = Date.now();
    
    // TODO: Gọi từng hàm một cách tuần tự
    // Tính toán tổng thời gian loading
    
    // Gợi ý: Sử dụng nested setTimeout hoặc cách khác
}

// Bài Tập 2b: Cách tải SONG SONG (parallel)
function taiDashboardSongSong() {
    console.log('⚡ === LOADING SONG SONG ===');
    const startTime = Date.now();
    
    // TODO: Gọi tất cả hàm cùng lúc
    // So sánh thời gian với cách tuần tự
    
    // Gợi ý: Gọi tất cả hàm cùng lúc, không chờ đợi
}

// TODO: Test cả hai cách và so sánh performance
// taiDashboardTuanTu();
// setTimeout(() => taiDashboardSongSong(), 5000);

💡 Hướng dẫn làm bài

  1. Bài 1: Copy code vào console trình duyệt, hoàn thành các TODO, chạy và quan sát sự khác biệt
  2. Bài 2: Tập trung vào việc hiểu loading tuần tự vs song song, đo thời gian thực tế
  3. Quan sát: Chú ý đến thời gian blocking và non-blocking behavior

2. 📞 Hàm Callback - Phương pháp đầu tiên xử lý bất đồng bộ

🔄 Callback Functions (Hàm gọi lại)
Callback là một hàm (function) được truyền như một tham số (argument) vào một hàm khác, và được gọi thực thi khi tác vụ bất đồng bộ hoàn thành. Đây là cách đầu tiên JavaScript sử dụng để xử lý các tác vụ bất đồng bộ.

🎯 Callback hoạt động như thế nào?

💡 Nguyên lý hoạt động của Callback

Hãy tưởng tượng bạn gọi điện đặt pizza:

  1. Bạn gọi điện: "Tôi muốn đặt 1 pizza pepperoni"
  2. Nhà hàng: "Ok, khoảng 30 phút nữa sẽ có. Cho tôi số điện thoại để gọi lại khi sẵn sàng"
  3. Bạn: "Số của tôi là 0123456789" (đây chính là CALLBACK)
  4. Bạn tiếp tục: Làm việc khác trong khi chờ
  5. 30 phút sau: Nhà hàng gọi lại số của bạn (thực thi CALLBACK)

Trong lập trình, số điện thoại = callback function, nhà hàng gọi lại = JavaScript engine thực thi callback

Ví dụ Callback cơ bản với giải thích từng dòng
JavaScript
// Định nghĩa hàm với tham số callback
function layThongTinNguoiDung(userId, callbackFunction) {
    console.log(`🔍 Đang lấy thông tin người dùng có ID: ${userId}...`);
    
    // Mô phỏng việc gọi API bằng setTimeout
    // setTimeout(function, delay) - thực thi function sau delay milliseconds
    setTimeout(() => {
        // Giả lập dữ liệu trả về từ server
        const thongTinNguoiDung = {
            id: userId,
            ten: 'Nguyễn Văn A',
            email: 'nguyenvana@example.com',
            tuoi: 25,
            diaChi: 'Hà Nội, Việt Nam'
        };
        
        // Gọi callback function và truyền dữ liệu vào
        // Đây là lúc "nhà hàng gọi lại"
        callbackFunction(thongTinNguoiDung);
    }, 2000); // 2000ms = 2 giây
}

// Định nghĩa callback function - "số điện thoại" của chúng ta
function xuLyKetQua(duLieuNguoiDung) {
    console.log('✅ Đã nhận được thông tin người dùng:');
    console.log(`   👤 Tên: ${duLieuNguoiDung.ten}`);
    console.log(`   📧 Email: ${duLieuNguoiDung.email}`);
    console.log(`   🎂 Tuổi: ${duLieuNguoiDung.tuoi}`);
    console.log(`   🏠 Địa chỉ: ${duLieuNguoiDung.diaChi}`);
    console.log(`🎉 Xin chào ${duLieuNguoiDung.ten}! Chào mừng bạn đến với hệ thống.`);
}

// Sử dụng hàm với callback
console.log('🚀 Bắt đầu quá trình lấy thông tin...');
layThongTinNguoiDung(123, xuLyKetQua);
console.log('💡 Tôi có thể làm việc khác trong khi chờ đợi...');
console.log('⚡ Code này chạy ngay lập tức, không bị chặn!');

// Kết quả sẽ in ra theo thứ tự:
// 🚀 Bắt đầu quá trình lấy thông tin...
// 🔍 Đang lấy thông tin người dùng có ID: 123...
// 💡 Tôi có thể làm việc khác trong khi chờ đợi...
// ⚡ Code này chạy ngay lập tức, không bị chặn!
// (sau 2 giây)
// ✅ Đã nhận được thông tin người dùng:
//    👤 Tên: Nguyễn Văn A
//    📧 Email: nguyenvana@example.com
//    🎂 Tuổi: 25
//    🏠 Địa chỉ: Hà Nội, Việt Nam
// 🎉 Xin chào Nguyễn Văn A! Chào mừng bạn đến với hệ thống.

� Callback với xử lý lỗi (Error Handling)

Callback với Error Handling - Mẫu phổ biến
JavaScript
// Mẫu callback chuẩn: (error, data) => {}
// Tham số đầu tiên luôn là error, tham số thứ hai là dữ liệu
function layDuLieuTuServer(userId, callback) {
    console.log(`🌐 Đang gửi request lên server để lấy user ${userId}...`);
    
    setTimeout(() => {
        // Mô phỏng tình huống có thể xảy ra lỗi
        const coLoi = Math.random() < 0.3; // 30% khả năng bị lỗi
        
        if (coLoi) {
            // Trường hợp LỖI: tham số đầu tiên là error, thứ hai là null
            const loi = new Error(`Không thể kết nối đến server. User ID ${userId} không tồn tại.`);
            callback(loi, null);
        } else {
            // Trường hợp THÀNH CÔNG: tham số đầu tiên là null, thứ hai là data
            const userData = {
                id: userId,
                ten: 'Trần Thị B',
                email: 'tranthib@example.com',
                trangThai: 'hoạt động'
            };
            callback(null, userData);
        }
    }, 1500);
}

// Cách sử dụng callback với error handling
layDuLieuTuServer(456, function(error, data) {
    // Luôn kiểm tra error trước
    if (error) {
        console.error('❌ Có lỗi xảy ra:', error.message);
        console.log('🔄 Có thể thử lại sau hoặc thông báo cho user');
        return; // Dừng thực thi nếu có lỗi
    }
    
    // Nếu không có lỗi, xử lý dữ liệu
    console.log('✅ Thành công! Nhận được dữ liệu:', data);
    console.log(`👋 Xin chào ${data.ten}!`);
    console.log(`📧 Email: ${data.email}`);
    console.log(`🟢 Trạng thái: ${data.trangThai}`);
});

💥 Vấn đề "Callback Hell" (Địa ngục Callback)

⚠️ Callback Hell là gì?

Callback Hell (hay còn gọi là "Pyramid of Doom" - Kim tự tháp tận thế) là tình trạng khi có nhiều callback lồng nhau (nested callbacks), khiến code trở nên:

  • Khó đọc: Code bị lồng sâu, nhìn như kim tự tháp nằm nghiêng
  • Khó maintain: Sửa một chỗ có thể ảnh hưởng nhiều nơi
  • Khó debug: Khó tìm ra lỗi khi có nhiều tầng callback
  • Khó test: Khó viết unit test cho code phức tạp như vậy
Ví dụ Callback Hell - Tình huống thực tế về E-commerce
JavaScript
// ❌ CALLBACK HELL - Ví dụ thực tế: Xử lý đơn hàng E-commerce
// Tình huống: Khi user mua hàng, cần thực hiện nhiều bước liên tiếp

layThongTinNguoiDung(userId, function(error, nguoiDung) {
    if (error) {
        console.error('❌ Lỗi lấy thông tin user:', error);
        return;
    }
    
    // Bước 2: Lấy lịch sử đơn hàng của user
    layLichSuDonHang(nguoiDung.id, function(error, danhSachDonHang) {
        if (error) {
            console.error('❌ Lỗi lấy lịch sử đơn hàng:', error);
            return;
        }
        
        // Bước 3: Lấy chi tiết đơn hàng gần nhất
        layChiTietDonHang(danhSachDonHang[0].id, function(error, chiTietDonHang) {
            if (error) {
                console.error('❌ Lỗi lấy chi tiết đơn hàng:', error);
                return;
            }
            
            // Bước 4: Lấy thông tin vận chuyển
            layThongTinVanChuyen(chiTietDonHang.vanChuyenId, function(error, vanChuyen) {
                if (error) {
                    console.error('❌ Lỗi lấy thông tin vận chuyển:', error);
                    return;
                }
                
                // Bước 5: Tính tổng chi phí
                tinhTongChiPhi(vanChuyen, function(error, tongChiPhi) {
                    if (error) {
                        console.error('❌ Lỗi tính tổng chi phí:', error);
                        return;
                    }
                    
                    // Bước 6: Áp dụng mã giảm giá (nếu có)
                    apDungMaGiamGia(tongChiPhi, nguoiDung.maGiamGia, function(error, giaThucTe) {
                        if (error) {
                            console.error('❌ Lỗi áp dụng mã giảm giá:', error);
                            return;
                        }
                        
                        // Bước 7: Thực hiện thanh toán
                        thucHienThanhToan(giaThucTe, function(error, ketQuaThanhToan) {
                            if (error) {
                                console.error('❌ Lỗi thanh toán:', error);
                                return;
                            }
                            
                            // CUỐI CÙNG: In kết quả
                            console.log('🎉 Thành công!');
                            console.log(`💰 Tổng tiền: ${giaThucTe.toLocaleString('vi-VN')} VNĐ`);
                            console.log(`🧾 Mã giao dịch: ${ketQuaThanhToan.maGiaoDich}`);
                            
                            // Còn có thể có thêm nhiều bước nữa...
                            // → Code ngày càng lồng sâu hơn!
                        });
                    });
                });
            });
        });
    });
});

// ⚠️ VẤN ĐỀ VỚI CODE TRÊN:
// 1. Code lồng 7 tầng sâu - khó đọc
// 2. Lặp lại error handling ở mỗi tầng
// 3. Khó sửa đổi logic ở giữa
// 4. Khó viết test
// 5. Khó debug khi có lỗi
Vấn đề Error Handling trong Callback Hell
JavaScript
// ❌ Error Handling phức tạp và lặp lại trong Callback Hell
function xuLyDonHangPhucTap(userId) {
    layThongTinNguoiDung(userId, function(error, nguoiDung) {
        if (error) {
            console.error('Bước 1 - Lỗi lấy user:', error.message);
            thongBaoLoiChoNguoiDung('Không thể lấy thông tin tài khoản');
            return;
        }
        
        layLichSuDonHang(nguoiDung.id, function(error, donHangList) {
            if (error) {
                console.error('Bước 2 - Lỗi lấy đơn hàng:', error.message);
                thongBaoLoiChoNguoiDung('Không thể lấy lịch sử đơn hàng');
                return;
            }
            
            if (donHangList.length === 0) {
                console.log('User chưa có đơn hàng nào');
                hienThiTrangDonHangTrong();
                return;
            }
            
            layChiTietDonHang(donHangList[0].id, function(error, chiTiet) {
                if (error) {
                    console.error('Bước 3 - Lỗi lấy chi tiết:', error.message);
                    thongBaoLoiChoNguoiDung('Không thể lấy chi tiết đơn hàng');
                    return;
                }
                
                // Kiểm tra validation
                if (!chiTiet.sanPham || chiTiet.sanPham.length === 0) {
                    console.error('Đơn hàng không có sản phẩm');
                    thongBaoLoiChoNguoiDung('Đơn hàng không hợp lệ');
                    return;
                }
                
                // ... Cứ thế lặp lại với mỗi tầng callback
                // Code trở nên RẤT KHUÔN và khó maintain!
            });
        });
    });
}

// ⚠️ Các vấn đề:
// - Lặp lại pattern if(error) ở mỗi tầng
// - Khó xử lý lỗi tổng thể
// - Không thể sử dụng try/catch
// - Mỗi callback phải tự xử lý lỗi riêng

✅ Ưu điểm Callbacks

  • Đơn giản, dễ hiểu với beginners
  • Supported natively trong JavaScript
  • Linh hoạt cho simple async operations
  • Không cần thư viện bổ sung

❌ Nhược điểm Callbacks

  • Callback Hell - code khó đọc
  • Error handling phức tạp
  • Khó debug và test
  • Không thể sử dụng try/catch
  • Khó compose và reuse
    Compose: Khó kết hợp nhiều hàm callback nhỏ thành một quy trình lớn hơn.

📝 Bài Tập Ôn Tập - Callbacks

🎯 Bài Tập 1: Tạo hệ thống đăng nhập với Callbacks

Mục tiêu: Hiểu cách sử dụng callback với error handling trong tình huống thực tế

Yêu cầu: Tạo hàm dangNhap(username, password, callback) mô phỏng quá trình đăng nhập:

  • Nếu username = "admin" và password = "123456" → thành công
  • Nếu username hoặc password sai → báo lỗi tương ứng
  • Có delay 2 giây để mô phỏng việc kiểm tra với server
  • Callback theo pattern: (error, userData)
Bài Tập 1 - Code mẫu để hoàn thành
JavaScript
// TODO: Hoàn thành hàm dangNhap
function dangNhap(username, password, callback) {
    console.log(`🔐 Đang kiểm tra thông tin đăng nhập cho: ${username}...`);
    
    // TODO: Sử dụng setTimeout để mô phỏng delay 2 giây
    setTimeout(() => {
        // TODO: Kiểm tra username và password
        // Nếu username === "admin" && password === "123456":
        //   - Gọi callback(null, {id: 1, username: "admin", role: "administrator"})
        // Nếu username !== "admin":
        //   - Gọi callback(new Error("Tên đăng nhập không tồn tại"), null)
        // Nếu password !== "123456":
        //   - Gọi callback(new Error("Mật khẩu không chính xác"), null)
        
    }, 2000);
}

// TODO: Sử dụng hàm dangNhap với các test case:
// Test case 1: Đăng nhập thành công
dangNhap("admin", "123456", function(error, userData) {
    // TODO: Xử lý kết quả
});

// Test case 2: Sai username
dangNhap("user", "123456", function(error, userData) {
    // TODO: Xử lý lỗi
});

// Test case 3: Sai password
dangNhap("admin", "wrongpass", function(error, userData) {
    // TODO: Xử lý lỗi
});

🎯 Bài Tập 2: Xử lý chuỗi Callbacks - Quản lý thư viện

Mục tiêu: Thực hành callback hell và hiểu vấn đề của nó

Tình huống: Hệ thống quản lý thư viện - mượn sách cần các bước:

  1. Kiểm tra thông tin thẻ thư viện
  2. Tìm kiếm sách trong hệ thống
  3. Kiểm tra sách có sẵn không
  4. Tạo phiếu mượn
Bài Tập 2 - Code khởi đầu để phát triển
JavaScript
// Các hàm helper đã có sẵn
function kiemTraTheDocGia(soThe, callback) {
    setTimeout(() => {
        if (soThe === "DG001") {
            callback(null, {soThe: "DG001", ten: "Nguyễn Văn A", trangThai: "hoạt động"});
        } else {
            callback(new Error("Thẻ không hợp lệ hoặc đã bị khóa"), null);
        }
    }, 1000);
}

function timKiemSach(tenSach, callback) {
    setTimeout(() => {
        if (tenSach === "JavaScript cơ bản") {
            callback(null, {id: "BOOK001", ten: "JavaScript cơ bản", tacGia: "Nguyễn Văn B"});
        } else {
            callback(new Error("Không tìm thấy sách"), null);
        }
    }, 1200);
}

function kiemTraTinhTrang(bookId, callback) {
    setTimeout(() => {
        // Giả lập 70% sách có sẵn
        const coSan = Math.random() < 0.7;
        if (coSan) {
            callback(null, {tinhTrang: "có sẵn", soLuong: 3});
        } else {
            callback(new Error("Sách đã được mượn hết"), null);
        }
    }, 800);
}

function taoPhieuMuon(docGia, sach, callback) {
    setTimeout(() => {
        const phieuMuon = {
            maPhieu: "PM" + Date.now(),
            docGia: docGia.ten,
            sach: sach.ten,
            ngayMuon: new Date().toLocaleDateString('vi-VN'),
            hanTra: new Date(Date.now() + 14*24*60*60*1000).toLocaleDateString('vi-VN')
        };
        callback(null, phieuMuon);
    }, 1000);
}

// TODO: Sử dụng tất cả hàm trên để tạo quy trình mượn sách hoàn chỉnh
// Gợi ý: Bắt đầu với kiemTraTheDocGia, sau đó lồng các callback
function muonSach(soThe, tenSach) {
    console.log(`📚 Bắt đầu quy trình mượn sách: "${tenSach}" cho thẻ ${soThe}`);
    
    // TODO: Hoàn thành callback hell ở đây
    kiemTraTheDocGia(soThe, function(error, docGia) {
        if (error) {
            console.error("❌ Lỗi kiểm tra thẻ:", error.message);
            return;
        }
        
        timKiemSach(tenSach, function(error, sach) {
            if (error) {
                console.error("❌ Lỗi tìm sách:", error.message);
                return;
            }
            
            kiemTraTinhTrang(sach.id, function(error, tinhTrang) {
                if (error) {
                    console.error("❌ Lỗi kiểm tra tình trạng:", error.message);
                    return;
                }
                
                taoPhieuMuon(docGia, sach, function(error, phieuMuon) {
                    if (error) {
                        console.error("❌ Lỗi tạo phiếu mượn:", error.message);
                        return;
                    }
                    
                    console.log("🎉 Mượn sách thành công!");
                    console.log("📋 Thông tin phiếu mượn:", phieuMuon);
                });
            });
        });
    });
}

// Test quy trình mượn sách
muonSach("DG001", "JavaScript cơ bản");

🔍 Quan sát và phân tích

Sau khi hoàn thành bài tập 2, hãy chú ý:

  • Độ lồng sâu: Code lồng 4 tầng, khó đọc
  • Error handling: Phải lặp lại if(error) 4 lần
  • Khó modify: Muốn thêm bước mới phải lồng thêm
  • Debugging: Khó trace lỗi khi có 4 tầng callback

3. 🤝 Promises - Giải pháp cho Callback Hell

⭐ Promise Object (Đối tượng Lời hứa)
Promise là một đối tượng (object) trong JavaScript đại diện cho kết quả cuối cùng (hoặc thất bại) của một tác vụ bất đồng bộ. Nó giống như một "lời hứa" rằng sẽ có kết quả trong tương lai - có thể thành công hoặc thất bại.

🧠 Hiểu Promise qua ví dụ thực tế

💡 Promise như "Lời hứa" trong đời sống

Hãy tưởng tượng bạn hẹn bạn thân đi xem phim:

  1. Đưa ra lời hứa: "Tôi hứa sẽ đi xem phim với bạn vào 7h tối nay"
  2. Trạng thái chờ đợi (Pending): Hiện tại là 3h chiều, lời hứa vẫn đang "chờ xử lý"
  3. Kết quả có thể xảy ra:
    • Thành công (Fulfilled): 7h tối bạn đến đúng hẹn → Lời hứa được thực hiện
    • Thất bại (Rejected): Bạn bị ốm không đi được → Lời hứa bị phá vỡ

Trong lập trình, Promise hoạt động tương tự!

📊 Các trạng thái của Promise

Trạng thái Tên tiếng Anh Mô tả chi tiết Có thể chuyển sang Ví dụ thực tế
🟡 Đang chờ Pending Trạng thái ban đầu, tác vụ đang thực hiện, chưa biết kết quả Fulfilled hoặc Rejected Đang gửi request lên server, chờ phản hồi
🟢 Thành công Fulfilled Tác vụ hoàn thành thành công, có kết quả trả về Không thể thay đổi (bất biến) API trả về dữ liệu thành công
🔴 Thất bại Rejected Tác vụ thất bại, có lỗi xảy ra Không thể thay đổi (bất biến) Lỗi mạng, server 500, không tìm thấy...

⚠️ Quy tắc quan trọng về Promise

  • Một Promise chỉ có thể thay đổi trạng thái MỘT LẦN
  • Khi đã Fulfilled hoặc Rejected, không thể thay đổi lại
  • Promise luôn bất đồng bộ, dù kết quả có ngay lập tức

🛠️ Cách tạo và sử dụng Promise

Tạo Promise cơ bản - Giải thích từng bước
JavaScript
// Cách tạo Promise với constructor
function layThongTinNguoiDung(userId) {
    // Trả về một Promise mới
    return new Promise((resolve, reject) => {
        // resolve: hàm gọi khi thành công
        // reject: hàm gọi khi thất bại
        
        console.log(`🔍 Bắt đầu lấy thông tin user ${userId}...`);
        
        // Mô phỏng tác vụ bất đồng bộ với setTimeout
        setTimeout(() => {
            // Giả lập điều kiện thành công/thất bại
            if (userId > 0) {
                // ✅ Trường hợp THÀNH CÔNG
                const thongTinUser = {
                    id: userId,
                    ten: 'Lê Văn C',
                    email: 'levanc@example.com',
                    tuoi: 28,
                    diaChi: 'TP.HCM',
                    soDienThoai: '0987654321'
                };
                
                // Gọi resolve() để báo hiệu thành công
                // Dữ liệu truyền vào resolve sẽ được truyền cho .then()
                resolve(thongTinUser);
                
            } else {
                // ❌ Trường hợp THẤT BẠI
                const loi = new Error(`User ID không hợp lệ: ${userId}. ID phải là số dương.`);
                
                // Gọi reject() để báo hiệu thất bại
                // Error truyền vào reject sẽ được truyền cho .catch()
                reject(loi);
            }
        }, 1500); // Delay 1.5 giây
    });
}

// Cách sử dụng Promise với .then() và .catch()
console.log('🚀 Bắt đầu process...');

layThongTinNguoiDung(123)
    .then(function(thongTinUser) {
        // .then() nhận kết quả từ resolve()
        console.log('✅ Thành công! Nhận được thông tin user:');
        console.log(`   👤 Tên: ${thongTinUser.ten}`);
        console.log(`   📧 Email: ${thongTinUser.email}`);
        console.log(`   🎂 Tuổi: ${thongTinUser.tuoi}`);
        console.log(`   🏠 Địa chỉ: ${thongTinUser.diaChi}`);
        
        // Giá trị return từ .then() sẽ được truyền cho .then() tiếp theo
        return `Xin chào ${thongTinUser.ten}!`;
    })
    .then(function(loiChao) {
        // .then() thứ hai nhận giá trị return từ .then() đầu tiên
        console.log('👋', loiChao);
        console.log('🎉 Đã hiển thị thông tin thành công!');
    })
    .catch(function(loi) {
        // .catch() nhận error từ reject() hoặc lỗi trong .then()
        console.error('❌ Có lỗi xảy ra:', loi.message);
        console.log('🔄 Có thể thử lại với user ID khác');
    })
    .finally(function() {
        // .finally() luôn chạy, dù thành công hay thất bại
        console.log('🔄 Quá trình xử lý hoàn tất (cleanup code)');
        console.log('📝 Ghi log, đóng kết nối, etc...');
    });

console.log('💡 Dòng này chạy ngay lập tức, không bị chặn!');

// Thử với user ID không hợp lệ
layThongTinNguoiDung(-1)
    .then(user => console.log('Không bao giờ chạy'))
    .catch(error => console.error('💥 Lỗi với user ID âm:', error.message));

🔗 Promise Chaining - Chuỗi Promise

💡 Promise Chaining là gì?

Promise Chaining là việc nối nhiều Promise lại với nhau bằng cách sử dụng .then(). Điều này giúp:

  • Giải quyết Callback Hell: Code không còn lồng sâu
  • Dễ đọc hơn: Logic chảy từ trên xuống dưới
  • Dễ maintain: Thêm/sửa bước dễ dàng
  • Error handling tốt hơn: Một .catch() có thể bắt lỗi của toàn bộ chuỗi
Promise Chaining - Giải quyết Callback Hell
JavaScript
// ✅ SỬ DỤNG PROMISE CHAINING - Giải quyết Callback Hell
// Cùng tình huống E-commerce nhưng với Promise

// Các hàm helper trả về Promise
function layThongTinNguoiDung(userId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (userId > 0) {
                resolve({ id: userId, ten: 'Nguyễn Văn D', email: 'user@example.com' });
            } else {
                reject(new Error('User ID không hợp lệ'));
            }
        }, 1000);
    });
}

function layLichSuDonHang(userId) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve([
                { id: 'DH001', tongTien: 500000, trangThai: 'hoàn thành' },
                { id: 'DH002', tongTien: 750000, trangThai: 'đang giao' }
            ]);
        }, 800);
    });
}

function layChiTietDonHang(donHangId) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({
                id: donHangId,
                sanPham: [
                    { ten: 'Laptop Dell', gia: 15000000, soLuong: 1 },
                    { ten: 'Chuột không dây', gia: 200000, soLuong: 2 }
                ],
                phiVanChuyen: 50000,
                vanChuyenId: 'VC123'
            });
        }, 600);
    });
}

function tinhTongChiPhi(chiTietDonHang) {
    return new Promise((resolve) => {
        setTimeout(() => {
            let tongTienSanPham = chiTietDonHang.sanPham.reduce((tong, sp) => {
                return tong + (sp.gia * sp.soLuong);
            }, 0);
            
            let tongChiPhi = tongTienSanPham + chiTietDonHang.phiVanChuyen;
            resolve(tongChiPhi);
        }, 400);
    });
}

// Sử dụng Promise Chaining thay vì Callback Hell
console.log('🚀 Bắt đầu xử lý đơn hàng với Promise...');

layThongTinNguoiDung(123)
    .then(nguoiDung => {
        console.log('✅ Bước 1: Lấy thông tin user thành công');
        console.log(`   👤 Tên: ${nguoiDung.ten}`);
        
        // Return Promise cho bước tiếp theo

        return layLichSuDonHang(nguoiDung.id);
    })
    .then(danhSachDonHang => {
        console.log('✅ Bước 2: Lấy lịch sử đơn hàng thành công');
        console.log(`   📦 Có ${danhSachDonHang.length} đơn hàng`);
        
        if (danhSachDonHang.length === 0) {
            throw new Error('Người dùng chưa có đơn hàng nào');
        }
        
        // Return Promise cho bước tiếp theo
        return layChiTietDonHang(danhSachDonHang[0].id);
    })
    .then(chiTietDonHang => {
        console.log('✅ Bước 3: Lấy chi tiết đơn hàng thành công');
        console.log(`   🛍️ Có ${chiTietDonHang.sanPham.length} sản phẩm`);
        
        // Return Promise cho bước tiếp theo
        return tinhTongChiPhi(chiTietDonHang);
    })
    .then(tongChiPhi => {
        console.log('✅ Bước 4: Tính tổng chi phí thành công');
        console.log(`💰 Tổng tiền: ${tongChiPhi.toLocaleString('vi-VN')} VNĐ`);
        
        return `Xử lý đơn hàng hoàn tất với tổng tiền ${tongChiPhi.toLocaleString('vi-VN')} VNĐ`;
    })
    .then(ketQua => {
        console.log('🎉', ketQua);
    })
    .catch(loi => {
        // MỘT .catch() duy nhất xử lý TẤT CẢ lỗi trong chuỗi
        console.error('❌ Có lỗi xảy ra trong quá trình xử lý:', loi.message);
        console.log('🔄 Có thể thử lại hoặc thông báo cho user');
    })
    .finally(() => {
        console.log('🔄 Hoàn tất quá trình xử lý đơn hàng');
    });

console.log('💡 Code này chạy ngay, không bị chặn!');

// ✨ LỢI ÍCH CỦA PROMISE CHAINING:
// 1. Code phẳng, không lồng sâu
// 2. Dễ đọc từ trên xuống dưới
// 3. Error handling đơn giản với một .catch()
// 4. Dễ thêm/bớt bước trong chuỗi
// 5. Có thể tái sử dụng từng hàm

🛠️ Tổng quan về Promise Utilities

Promise utilities là các hàm tĩnh mạnh mẽ giúp xử lý nhiều Promise cùng lúc, tối ưu cho các tác vụ song song hoặc cần kiểm soát trạng thái của nhiều promise.

Hàm Mô tả Kết quả trả về (Ví dụ cụ thể) Xử lý lỗi
Promise.all Chờ tất cả promises fulfilled. Nếu có 1 promise rejected thì reject ngay. ✅ Thành công:
[value1, value2, value3]
VD: ['user data', 'posts data', 'stats data']

❌ Thất bại:
Error của promise đầu tiên fail
VD: Error('API timeout')
Chỉ cần 1 promise lỗi là catch luôn, không có kết quả nào trả về.
Promise.race Trả về kết quả của promise hoàn thành (fulfilled/rejected) đầu tiên. 🏆 Winner thành công:
value của promise nhanh nhất
VD: { server: 'A', data: '...' }

💥 Winner thất bại:
Error của promise nhanh nhất
VD: Error('Connection timeout')
Nếu promise đầu tiên bị reject thì catch luôn.
Promise.allSettled Chờ tất cả promises hoàn thành (dù thành công hay thất bại). 📊 Luôn trả mảng objects:
[
  {status: 'fulfilled', value: 'data1'},
  {status: 'rejected', reason: Error},
  {status: 'fulfilled', value: 'data3'}
]

Báo cáo chi tiết từng promise
Không bao giờ reject, luôn trả về trạng thái từng promise.

📋 So sánh nhanh với ví dụ cụ thể

Giả sử có 3 promises: getUserInfo(), getPosts(), getStats()

Tình huống Promise.all Promise.race Promise.allSettled
Tất cả thành công [userInfo, posts, stats]
✅ Mảng 3 kết quả
stats
🏃‍♂️ Chỉ promise nhanh nhất
[{status:'fulfilled', value:userInfo}, ...]
📊 Mảng 3 objects chi tiết
getUserInfo() bị lỗi Error('User not found')
❌ Catch ngay, không có data
stats hoặc Error
⚡ Tùy promise nào về trước
[{status:'rejected', reason:Error}, {status:'fulfilled', value:posts}, ...]
📋 Vẫn có đầy đủ báo cáo

1️⃣ Promise.all() - Chi tiết cách xử lý data và error

  • Chờ tất cả promises fulfilled. Nếu có 1 promise rejected thì Promise.all sẽ reject ngay với lỗi đầu tiên.
  • Trả về mảng kết quả theo đúng thứ tự promises truyền vào.
  • Ứng dụng: Khi cần tất cả kết quả thành công, ví dụ: tải nhiều API, upload nhiều file.
  • Khi TẤT CẢ thành công: .then(results) nhận mảng kết quả [value1, value2, ...] theo đúng thứ tự
  • Khi CÓ 1 promise fail: .catch(error) nhận lỗi của promise đầu tiên bị reject, KHÔNG có kết quả nào cả
  • Thứ tự kết quả: Luôn giữ nguyên thứ tự promises truyền vào, dù promise nào hoàn thành trước
Promise.all() - Ví dụ chi tiết về xử lý data và error
JavaScript
// 🎯 Promise.all() - Xử lý data và error chi tiết

// Tạo các promise mô phỏng API calls với thời gian khác nhau
function layThongTinUser(userId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (userId > 0) {
                resolve({ id: userId, name: `User ${userId}`, email: `user${userId}@example.com` });
            } else {
                reject(new Error(`Invalid userId: ${userId}`));
            }
        }, 1000); // 1 giây
    });
}

function layBaiViet(userId) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve([
                { id: 1, title: 'Bài viết 1', author: userId },
                { id: 2, title: 'Bài viết 2', author: userId }
            ]);
        }, 2000); // 2 giây - chậm hơn
    });
}

function layThongKe(userId) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ 
                views: 1500, 
                likes: 89, 
                comments: 23,
                userId: userId 
            });
        }, 500); // 0.5 giây - nhanh nhất
    });
}

// ✅ TRƯỜNG HỢP 1: TẤT CẢ PROMISES THÀNH CÔNG
console.log('📊 Test 1: Tất cả promises thành công');
Promise.all([
    layThongTinUser(123),   // Promise 1 - hoàn thành sau 1s
    layBaiViet(123),        // Promise 2 - hoàn thành sau 2s (chậm nhất)
    layThongKe(123)         // Promise 3 - hoàn thành sau 0.5s (nhanh nhất)
])
.then(results => {
    // 📥 NHẬN DỮ LIỆU: Mảng kết quả theo đúng thứ tự
    console.log('✅ Tất cả thành công! Nhận được:', results);
    
    const [userInfo, baiVietList, thongKe] = results; // Destructuring
    console.log('👤 User info:', userInfo);           // results[0]
    console.log('📝 Bài viết:', baiVietList);         // results[1] 
    console.log('📊 Thống kê:', thongKe);             // results[2]
    
    // Lưu ý: Thứ tự luôn đúng dù promise 3 hoàn thành sớm nhất
    console.log('🎯 Thứ tự kết quả luôn cố định!');
})
.catch(error => {
    // Không bao giờ vào đây vì tất cả promises đều thành công
    console.error('❌ Có lỗi:', error.message);
});

// ❌ TRƯỜNG HỢP 2: CÓ 1 PROMISE BỊ REJECT
console.log('\n💥 Test 2: Có 1 promise bị reject');
Promise.all([
    layThongTinUser(-1),    // ❌ Promise này sẽ reject vì userId < 0
    layBaiViet(123),        // ✅ Promise này vẫn chạy bình thường
    layThongKe(123)         // ✅ Promise này vẫn chạy bình thường
])
.then(results => {
    // 🚫 KHÔNG BAO GIỜ VÀO ĐÂY vì có promise bị reject
    console.log('Không bao giờ thấy dòng này');
})
.catch(error => {
    // 📥 NHẬN LỖI: Chỉ nhận lỗi của promise đầu tiên bị reject
    console.error('❌ Promise.all bị reject:', error.message);
    console.log('💡 Lưu ý: Không nhận được KẾT QUẢ NÀO, dù các promise khác thành công');
    console.log('⚠️ Các promise khác vẫn tiếp tục chạy nhưng kết quả bị bỏ qua');
});

// 🔍 TRƯỜNG HỢP 3: NHIỀU PROMISES BỊ REJECT
console.log('\n💥💥 Test 3: Nhiều promises bị reject');
function promiseLoiSom() {
    return new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Lỗi sớm - 500ms')), 500);
    });
}

function promiseLoiCham() {
    return new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Lỗi chậm - 1500ms')), 1500);
    });
}

Promise.all([
    promiseLoiSom(),        // Reject sau 500ms
    layThongTinUser(456),   // Thành công sau 1000ms  
    promiseLoiCham()        // Reject sau 1500ms
])
.then(results => {
    console.log('Không bao giờ vào đây');
})
.catch(error => {
    // 📥 CHỈ NHẬN LỖI ĐẦU TIÊN: Lỗi của promise reject sớm nhất
    console.error('❌ Nhận lỗi đầu tiên:', error.message); // "Lỗi sớm - 500ms"
    console.log('💡 Dù có nhiều lỗi, chỉ catch lỗi của promise reject đầu tiên');
});

// 📋 TÓM TẮT CÁCH SỬ DỤNG Promise.all()
console.log(`
📋 TÓM TẮT Promise.all():
✅ Thành công: .then(results) nhận mảng [result1, result2, ...]
❌ Thất bại: .catch(error) nhận lỗi đầu tiên, KHÔNG có results
🎯 Thứ tự: Luôn giữ nguyên thứ tự promises đầu vào
⚡ Tốc độ: Chờ promise CHẬM NHẤT hoàn thành
💥 Fail-fast: Chỉ cần 1 promise reject là toàn bộ fail
`);

2️⃣ Promise.race() - Chi tiết cách xử lý data và error

  • Trả về kết quả của promise hoàn thành (fulfilled/rejected) đầu tiên.
  • Các promise còn lại vẫn tiếp tục chạy (không bị hủy).
  • Ứng dụng: Lấy kết quả nhanh nhất, timeout cho request, so sánh tốc độ nhiều server.

📥 Cách Promise.race() nhận và trả dữ liệu

  • Khi promise ĐẦU TIÊN thành công: .then(result) nhận giá trị của promise đó
  • Khi promise ĐẦU TIÊN thất bại: .catch(error) nhận lỗi của promise đó
  • Các promise khác: Vẫn tiếp tục chạy ngầm, nhưng kết quả bị bỏ qua
  • Chỉ quan tâm tốc độ: Promise nào về trước (dù thành công hay thất bại) là winner
Promise.race() - Ví dụ chi tiết về xử lý data và error
JavaScript
// 🏁 Promise.race() - Xử lý data và error chi tiết

// Tạo các promises với tốc độ khác nhau để test race
function serverA() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ server: 'A', data: 'Dữ liệu từ Server A', responseTime: '300ms' });
        }, 300);
    });
}

function serverB() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ server: 'B', data: 'Dữ liệu từ Server B', responseTime: '500ms' });
        }, 500);
    });
}

function serverC() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ server: 'C', data: 'Dữ liệu từ Server C', responseTime: '800ms' });
        }, 800);
    });
}

// ✅ TRƯỜNG HỢP 1: PROMISE NHANH NHẤT THÀNH CÔNG
console.log('🏁 Test 1: Promise nhanh nhất thành công');
Promise.race([
    serverA(),  // Thành công sau 300ms - NHANH NHẤT
    serverB(),  // Thành công sau 500ms
    serverC()   // Thành công sau 800ms
])
.then(result => {
    // 📥 CHỈ NHẬN KẾT QUẢ CỦA WINNER: Server A (nhanh nhất)
    console.log('🥇 Winner:', result);
    console.log(`📡 Server chiến thắng: ${result.server}`);
    console.log(`⚡ Response time: ${result.responseTime}`);
    console.log(`📦 Data: ${result.data}`);
    console.log('💡 Các server khác vẫn chạy nhưng kết quả bị bỏ qua');
})
.catch(error => {
    // Không vào đây vì promise nhanh nhất (Server A) thành công
    console.error('❌ Lỗi:', error.message);
});

// Tạo promises có lỗi để test fail case
function serverLoi() {
    return new Promise((_, reject) => {
        setTimeout(() => {
            reject(new Error('Server lỗi - Connection timeout'));
        }, 200); // 200ms - NHANH NHẤT nhưng BỊ LỖI
    });
}

function serverCham() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ server: 'Slow', data: 'Dữ liệu chậm nhưng đúng', responseTime: '1000ms' });
        }, 1000); // 1000ms - chậm nhưng thành công
    });
}

// ❌ TRƯỜNG HỢP 2: PROMISE NHANH NHẤT BỊ REJECT
console.log('\n💥 Test 2: Promise nhanh nhất bị reject');
Promise.race([
    serverLoi(),    // Reject sau 200ms - NHANH NHẤT
    serverCham(),   // Resolve sau 1000ms - chậm hơn
    serverC()       // Resolve sau 800ms
])
.then(result => {
    // 🚫 KHÔNG BAO GIỜ VÀO ĐÂY vì promise nhanh nhất bị reject
    console.log('Không bao giờ thấy dòng này');
})
.catch(error => {
    // 📥 NHẬN LỖI CỦA PROMISE NHANH NHẤT
    console.error('💥 Promise race bị reject:', error.message);
    console.log('⚠️ Dù có promises thành công sau đó, nhưng đã quá muộn');
    console.log('🏁 Promise.race đã kết thúc với lỗi của winner');
});

// 🔄 TRƯỜNG HỢP 3: MIX SUCCESS VÀ ERROR - AI NHANH HƠN?
function randomPromise(name, delay, willFail = false) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (willFail) {
                reject(new Error(`${name} failed sau ${delay}ms`));
            } else {
                resolve({ name, result: `${name} thành công sau ${delay}ms`, delay });
            }
        }, delay);
    });
}

console.log('\n🎲 Test 3: Random - ai sẽ thắng?');
Promise.race([
    randomPromise('Fast Success', 250, false),   // Thành công nhanh
    randomPromise('Fast Error', 300, true),      // Lỗi khá nhanh  
    randomPromise('Slow Success', 600, false)    // Thành công chậm
])
.then(result => {
    console.log('🎉 Thắng cuộc:', result);
    console.log('✅ Promise nhanh nhất thành công!');
})
.catch(error => {
    console.error('💀 Thua cuộc:', error.message);
    console.log('❌ Promise nhanh nhất bị lỗi!');
});

// 📊 SO SÁNH Promise.all() vs Promise.race()
console.log(`
📊 SO SÁNH Promise.all() vs Promise.race():

Promise.all():
✅ Chờ TẤT CẢ promises thành công → .then([result1, result2, ...])
❌ Có 1 promise lỗi → .catch(firstError), KHÔNG có kết quả nào
🎯 Dùng khi: Cần TẤT CẢ dữ liệu mới có thể tiếp tục

Promise.race():  
✅ Promise ĐẦU TIÊN thành công → .then(winnerResult)
❌ Promise ĐẦU TIÊN lỗi → .catch(winnerError)
🎯 Dùng khi: Chỉ cần 1 kết quả nhanh nhất, hoặc timeout mechanism

⚡ TIP: Dùng Promise.race() để tạo timeout cho Promise.all():
Promise.race([
    Promise.all([...promises]),
    new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000))
])
`);

// 🔧 PRACTICAL EXAMPLE: API với timeout
function apiWithTimeout(url, timeoutMs = 3000) {
    const apiCall = fetch(url).then(response => response.json());
    const timeout = new Promise((_, reject) => 
        setTimeout(() => reject(new Error('Request timeout')), timeoutMs)
    );
    
    return Promise.race([apiCall, timeout]);
}

// Usage: apiWithTimeout('/api/data', 2000)
//   .then(data => console.log('Got data:', data))
//   .catch(error => console.log('Failed or timeout:', error.message));

3️⃣ Promise.allSettled() - Chi tiết cách xử lý data và error

  • Chờ tất cả promises hoàn thành (dù thành công hay thất bại).
  • Luôn trả về mảng object với từng phần tử có dạng { status: 'fulfilled', value: ... } hoặc { status: 'rejected', reason: ... }.
  • Ứng dụng: Khi muốn biết trạng thái của tất cả promises, ví dụ: kiểm tra sức khỏe nhiều service, upload nhiều file và báo cáo từng file.

📥 Cách Promise.allSettled() nhận và trả dữ liệu

  • LUÔN VÀO .then(): Không bao giờ reject, luôn trả về mảng kết quả
  • Format kết quả thành công: { status: 'fulfilled', value: actualResult }
  • Format kết quả thất bại: { status: 'rejected', reason: errorObject }
  • Thứ tự: Giữ nguyên thứ tự promises đầu vào
  • Hoàn thiện 100%: Chờ tất cả promises kết thúc mới trả kết quả
Promise.allSettled() - Ví dụ chi tiết về xử lý data và error
JavaScript
// 🎯 Promise.allSettled() - Xử lý data và error chi tiết

// Tạo promises mix thành công và thất bại để test
function uploadFile(fileName, size) {
    return new Promise((resolve, reject) => {
        const uploadTime = Math.random() * 2000 + 500; // 500ms - 2.5s
        
        setTimeout(() => {
            // Giả lập: file lớn hơn 1MB có 30% khả năng fail
            const willFail = size > 1000000 && Math.random() < 0.3;
            
            if (willFail) {
                reject(new Error(`Upload failed: ${fileName} (${size} bytes) - Server error`));
            } else {
                resolve({
                    fileName,
                    size,
                    uploadTime: Math.round(uploadTime),
                    url: `https://cdn.example.com/${fileName}`,
                    status: 'uploaded'
                });
            }
        }, uploadTime);
    });
}

function validateData(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (data && data.length > 0) {
                resolve({ valid: true, itemCount: data.length, message: 'Data hợp lệ' });
            } else {
                reject(new Error('Dữ liệu không hợp lệ hoặc rỗng'));
            }
        }, 800);
    });
}

function sendNotification(userId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // 20% khả năng service email bị lỗi
            if (Math.random() < 0.2) {
                reject(new Error('Email service unavailable'));
            } else {
                resolve({ userId, sent: true, timestamp: new Date().toISOString() });
            }
        }, 1200);
    });
}

// 🏗️ TEST Promise.allSettled() với mix success/failure
console.log('📊 Test Promise.allSettled() - Upload nhiều files');

const uploadTasks = [
    uploadFile('avatar.jpg', 500000),        // File nhỏ - thường thành công
    uploadFile('video.mp4', 15000000),       // File lớn - có thể fail
    uploadFile('document.pdf', 2000000),     // File vừa - có thể fail
    validateData(['item1', 'item2']),        // Validate data - thường thành công
    sendNotification(123)                    // Send email - có thể fail
];

Promise.allSettled(uploadTasks)
    .then(results => {
        // 📥 LUÔN VÀO ĐÂY - Không bao giờ reject
        console.log('✅ Tất cả tasks đã hoàn thành! Phân tích kết quả:');
        console.log('📊 Raw results:', results);
        
        // Phân loại kết quả
        const successful = [];
        const failed = [];
        
        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                // 📥 XỬ LÝ KẾT QUẢ THÀNH CÔNG
                successful.push({
                    index,
                    data: result.value  // Dữ liệu thực từ resolve()
                });
                console.log(`✅ Task ${index + 1} thành công:`, result.value);
            } else {
                // 📥 XỬ LÝ KẾT QUẢ THẤT BẠI  
                failed.push({
                    index,
                    error: result.reason  // Error object từ reject()
                });
                console.error(`❌ Task ${index + 1} thất bại:`, result.reason.message);
            }
        });
        
        // 📈 BÁO CÁO TỔNG QUAN
        console.log(`\n📈 Báo cáo tổng quan:`);
        console.log(`   ✅ Thành công: ${successful.length}/${results.length}`);
        console.log(`   ❌ Thất bại: ${failed.length}/${results.length}`);
        console.log(`   📊 Tỷ lệ thành công: ${(successful.length/results.length*100).toFixed(1)}%`);
        
        // 🔧 XỬ LÝ THEO LOGIC BUSINESS
        if (successful.length === results.length) {
            console.log('🎉 Tất cả tasks thành công! Tiếp tục workflow...');
        } else if (successful.length > 0) {
            console.log('⚠️ Một số tasks thành công. Retry failed tasks...');
            console.log('🔄 Có thể retry:', failed.map(f => `Task ${f.index + 1}`).join(', '));
        } else {
            console.log('💀 Tất cả tasks đều thất bại. Kiểm tra hệ thống...');
        }
        
        return { successful, failed, totalTasks: results.length };
    })
    .catch(error => {
        // 🚫 KHÔNG BAO GIỜ VÀO ĐÂY vì allSettled không bao giờ reject
        console.log('Không bao giờ thấy dòng này với allSettled');
    });
 

📝 Bài Tập Ôn Tập - Promises

🎯 Bài Tập 3: Chuyển đổi Callbacks thành Promises

Mục tiêu: Hiểu cách convert callback-based functions thành Promise-based

Yêu cầu: Chuyển đổi hệ thống mượn sách từ bài tập 2 sang sử dụng Promises

Bài Tập 3 - Promisify các hàm callback
JavaScript
// TODO: Chuyển đổi các hàm callback thành Promise

// Ví dụ mẫu cho hàm đầu tiên
function kiemTraTheDocGiaPromise(soThe) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (soThe === "DG001") {
                resolve({soThe: "DG001", ten: "Nguyễn Văn A", trangThai: "hoạt động"});
            } else {
                reject(new Error("Thẻ không hợp lệ hoặc đã bị khóa"));
            }
        }, 1000);
    });
}

// TODO: Hoàn thành các hàm còn lại
function timKiemSachPromise(tenSach) {
    // TODO: Chuyển đổi timKiemSach thành Promise
    return new Promise((resolve, reject) => {
        // Your code here
    });
}

function kiemTraTinhTrangPromise(bookId) {
    // TODO: Chuyển đổi kiemTraTinhTrang thành Promise
    return new Promise((resolve, reject) => {
        // Your code here
    });
}

function taoPhieuMuonPromise(docGia, sach) {
    // TODO: Chuyển đổi taoPhieuMuon thành Promise
    return new Promise((resolve, reject) => {
        // Your code here
    });
}

// TODO: Sử dụng Promise chaining để tạo quy trình mượn sách
function muonSachVoiPromise(soThe, tenSach) {
    console.log(`📚 Bắt đầu quy trình mượn sách với Promise: "${tenSach}"`);
    
    // TODO: Sử dụng Promise chaining
    return kiemTraTheDocGiaPromise(soThe)
        .then(docGia => {
            console.log(`✅ Kiểm tra thẻ thành công: ${docGia.ten}`);
            // TODO: Return next Promise
            return timKiemSachPromise(tenSach);
        })
        .then(sach => {
            console.log(`✅ Tìm thấy sách: ${sach.ten}`);
            // TODO: Continue chain
        })
        // TODO: Continue with remaining steps
        .catch(error => {
            console.error("❌ Lỗi trong quá trình mượn sách:", error.message);
            throw error; // Re-throw để caller có thể handle
        });
}

// Test Promise version
muonSachVoiPromise("DG001", "JavaScript cơ bản")
    .then(result => console.log("🎉 Hoàn tất:", result))
    .catch(error => console.log("💥 Thất bại:", error.message));

🎯 Bài Tập 4: Promise Utilities - Xử lý nhiều Promises

Mục tiêu: Học cách sử dụng Promise.all(), Promise.race(), Promise.allSettled()

Yêu cầu: Tạo hệ thống kiểm tra sức khỏe website với multiple endpoints

Bài Tập 4 - Website Health Check System
// Bài Tập 4: Hệ thống Health Check cho nhiều services
const services = [
    { name: 'Database', url: '/api/health/db', timeout: 2000 },
    { name: 'Redis Cache', url: '/api/health/redis', timeout: 1000 },
    { name: 'Email Service', url: '/api/health/email', timeout: 3000 },
    { name: 'File Storage', url: '/api/health/storage', timeout: 1500 },
    { name: 'Payment Gateway', url: '/api/health/payment', timeout: 5000 }
];

// Mock function để check health của một service
function checkServiceHealth(service) {
    return new Promise((resolve, reject) => {
        const startTime = Date.now();
        
        // Mô phỏng network request với random success/failure
        setTimeout(() => {
            const isHealthy = Math.random() > 0.2; // 80% success rate
            const responseTime = Date.now() - startTime;
            
            if (isHealthy) {
                resolve({
                    service: service.name,
                    status: 'healthy',
                    responseTime: responseTime,
                    timestamp: new Date().toISOString()
                });
            } else {
                reject({
                    service: service.name,
                    status: 'unhealthy',
                    error: 'Service unavailable',
                    responseTime: responseTime,
                    timestamp: new Date().toISOString()
                });
            }
        }, Math.random() * service.timeout);
    });
}

// TODO: Bài Tập 4a - Sử dụng Promise.all()
// Kiểm tra TẤT CẢ services phải healthy mới pass
function checkAllServicesStrict() {
    console.log('🔍 Kiểm tra strict (tất cả phải healthy)...');
    
    const startTime = Date.now();
    
    // TODO: Sử dụng Promise.all() với services.map()
    return Promise.all(
        services.map(service => checkServiceHealth(service))
    )
    .then(results => {
        const totalTime = Date.now() - startTime;
        console.log('✅ Tất cả services healthy!');
        console.log(`⏱️ Tổng thời gian: ${totalTime}ms`);
        
        results.forEach(result => {
            console.log(`   ${result.service}: ${result.responseTime}ms`);
        });
        
        return { status: 'all_healthy', results, totalTime };
    })
    .catch(error => {
        console.log('❌ Có service bị lỗi:', error.service);
        throw new Error(`Health check failed: ${error.service} is unhealthy`);
    });
}

// TODO: Bài Tập 4b - Sử dụng Promise.race()
// Kiểm tra service nào response nhanh nhất
function checkFastestService() {
    console.log('🏃‍♂️ Tìm service nhanh nhất...');
    
    // TODO: Sử dụng Promise.race()
    return Promise.race(
        services.map(service => checkServiceHealth(service))
    )
    .then(fastest => {
        console.log(`🥇 Service nhanh nhất: ${fastest.service} (${fastest.responseTime}ms)`);
        return fastest;
    })
    .catch(error => {
        console.log('💥 Service nhanh nhất bị lỗi:', error.service);
        throw error;
    });
}

// TODO: Bài Tập 4c - Sử dụng Promise.allSettled()
// Kiểm tra TẤT CẢ và báo cáo chi tiết (không fail nếu có lỗi)
function checkAllServicesDetailed() {
    console.log('📊 Kiểm tra chi tiết tất cả services...');
    
    const startTime = Date.now();
    
    // TODO: Sử dụng Promise.allSettled()
    return Promise.allSettled(
        services.map(service => checkServiceHealth(service))
    )
    .then(results => {
        const totalTime = Date.now() - startTime;
        
        const healthyServices = results.filter(r => r.status === 'fulfilled');
        const unhealthyServices = results.filter(r => r.status === 'rejected');
        
        console.log(`📈 Báo cáo tổng quan:`);
        console.log(`   ✅ Healthy: ${healthyServices.length}/${services.length}`);
        console.log(`   ❌ Unhealthy: ${unhealthyServices.length}/${services.length}`);
        console.log(`   ⏱️ Tổng thời gian: ${totalTime}ms`);
        
        console.log(`\n📋 Chi tiết:`);
        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                const data = result.value;
                console.log(`   ✅ ${data.service}: ${data.responseTime}ms`);
            } else {
                const error = result.reason;
                console.log(`   ❌ ${error.service}: ${error.error}`);
            }
        });
        
        return {
            totalServices: services.length,
            healthyCount: healthyServices.length,
            unhealthyCount: unhealthyServices.length,
            totalTime,
            details: results
        };
    });
}

// TODO: Bài Tập 4d - Advanced: Retry mechanism với Promise
function checkWithRetry(service, maxRetries = 3) {
    function attemptCheck(currentAttempt) {
        console.log(`🔄 Thử lần ${currentAttempt} cho ${service.name}...`);
        
        return checkServiceHealth(service)
            .then(result => {
                console.log(`✅ ${service.name} healthy sau ${currentAttempt} lần thử`);
                return result;
            })
            .catch(error => {
                if (currentAttempt >= maxRetries) {
                    console.log(`💀 ${service.name} failed sau ${maxRetries} lần thử`);
                    throw error;
                }
                
                console.log(`⚠️ ${service.name} failed, thử lại...`);
                
                // Tạo delay 1 giây trước khi retry
                return new Promise(resolve => {
                    setTimeout(() => {
                        resolve(attemptCheck(currentAttempt + 1));
                    }, 1000);
                })
                .then(result => result); // Flatten promise
            });
    }
    
    return attemptCheck(1);
}

// Test functions - Sử dụng Promise chaining thay vì async/await
function runHealthCheckDemo() {
    console.log('🚀 Bắt đầu demo Health Check System\n');
    
    // Test 1: Promise.all (strict)
    return checkAllServicesStrict()
        .then(result => {
            console.log('✅ Strict check thành công:', result.status);
        })
        .catch(error => {
            console.log('❌ Strict check thất bại như dự kiến:', error.message);
        })
        .then(() => {
            console.log(''); // Empty line
            
            // Test 2: Promise.race (fastest)
            return checkFastestService();
        })
        .then(fastest => {
            console.log('🎉 Race check thành công!');
            console.log(''); // Empty line
        })
        .catch(error => {
            console.log('💥 Race check có lỗi:', error.message);
            console.log(''); // Empty line
        })
        .then(() => {
            // Test 3: Promise.allSettled (detailed)
            return checkAllServicesDetailed();
        })
        .then(detailedReport => {
            console.log('📊 AllSettled check hoàn thành!');
            console.log(`Tỷ lệ thành công: ${(detailedReport.healthyCount/detailedReport.totalServices*100).toFixed(1)}%`);
            console.log(''); // Empty line
        })
        .then(() => {
            // Test 4: Retry mechanism
            console.log('🔄 Test retry mechanism:');
            return checkWithRetry(services[0], 2);
        })
        .then(retryResult => {
            console.log('✅ Retry mechanism thành công:', retryResult.service);
        })
        .catch(error => {
            console.log('💀 Retry cũng thất bại:', error.message || error.service);
        })
        .finally(() => {
            console.log('\n🎉 Demo Health Check System hoàn tất!');
        });
}

// Chạy demo
runHealthCheckDemo();

3. 🔮 Async/Await - Cú pháp hiện đại nhất

⭐ Async/Await - Syntactic Sugar cho Promises
Async/Await là cú pháp hiện đại (ES2017) được xây dựng trên nền tảng Promises, giúp viết code bất đồng bộ trông giống như code đồng bộ. Nó KHÔNG phải là công nghệ mới, mà chỉ là cách viết đẹp hơn cho Promises.

🧠 Async/Await là gì và hoạt động như thế nào?

💡 Bản chất của Async/Await

Async/Await thực chất là "syntactic sugar" (cú pháp ngọt ngào) cho Promises:

  • Async: Biến một function thành function bất đồng bộ, luôn trả về Promise
  • Await: Tạm dừng thực thi function cho đến khi Promise được resolve/reject
  • Không thay đổi bản chất: Vẫn là bất đồng bộ, không chặn luồng chính
  • Dưới capot: Compiler chuyển async/await thành Promise chains

📝 Cú pháp cơ bản của Async/Await

Cú pháp Async/Await - Từ cơ bản đến nâng cao
JavaScript
// 1. CÚ PHÁP CƠ BẢN

// Khai báo async function
async function tenHam() {
    // Từ khóa 'async' trước 'function' 
    // → Function này luôn trả về Promise
}

// Sử dụng await (chỉ dùng được trong async function)
async function hamBatDongBo() {
    // await chỉ sử dụng được trong async function
    const ketQua = await promiseNaoDay();
    //     ↑           ↑
    //   tạm dừng   Promise object
    return ketQua;
}

// 2. CÁC CÁCH KHAI BÁO ASYNC FUNCTION

// Function declaration
async function myFunction() {
    return "Hello";
}

// Function expression
const myFunction2 = async function() {
    return "Hello";
};

// Arrow function
const myFunction3 = async () => {
    return "Hello";
};

// Method trong object
const obj = {
    async myMethod() {
        return "Hello";
    }
};

// Method trong class
class MyClass {
    async myMethod() {
        return "Hello";
    }
}

// 3. HIỂU SÂU VỀ ASYNC

// Hàm bình thường
function hamBinhThuong() {
    return "Hello World";
}

// Hàm async (tự động wrap trong Promise)
async function hamAsync() {
    return "Hello World"; // Tự động thành Promise.resolve("Hello World")
}

console.log(hamBinhThuong()); // "Hello World" (string)
console.log(hamAsync());      // Promise {: "Hello World"}

// Cách sử dụng kết quả của async function
hamAsync().then(result => console.log(result)); // "Hello World"

// Hoặc sử dụng await
async function main() {
    const result = await hamAsync();
    console.log(result); // "Hello World"
}

// 4. HIỂU SÂU VỀ AWAIT

async function demo() {
    console.log('1. Bắt đầu');
    
    // await làm function TẠM DỪNG tại đây
    // Chờ Promise resolve, sau đó tiếp tục
    const data = await fetch('/api/data'); // Dừng ở đây cho đến khi fetch xong
    
    console.log('2. Fetch hoàn thành');
    const json = await data.json(); // Lại dừng ở đây
    
    console.log('3. Parse JSON xong');
    return json;
}

// 5. QUY TẮC QUAN TRỌNG

// ❌ SAI: await chỉ dùng được trong async function
function hamSai() {
    const data = await fetch('/api'); // SyntaxError!
}

// ✅ ĐÚNG: await trong async function
async function hamDung() {
    const data = await fetch('/api'); // OK!
}

// ❌ SAI: async function vẫn cần await để lấy kết quả
async function layDuLieu() {
    return "Dữ liệu";
}

function main() {
    const data = layDuLieu(); // data là Promise, không phải "Dữ liệu"
    console.log(data); // Promise {: "Dữ liệu"}
}

// ✅ ĐÚNG: Sử dụng await hoặc .then()
async function mainDung() {
    const data = await layDuLieu(); // "Dữ liệu"
    console.log(data); // "Dữ liệu"
}

⚙️ Async/Await hoạt động như thế nào?

Cơ chế hoạt động của Async/Await - Step by step
JavaScript
// DEMO: Hiểu cơ chế hoạt động của async/await

// Tạo Promise để demo
function taoPromise(value, delay) {
    return new Promise((resolve) => {
        console.log(`   🟡 Bắt đầu Promise: "${value}" (${delay}ms)`);
        setTimeout(() => {
            console.log(`   🟢 Hoàn thành Promise: "${value}"`);
            resolve(value);
        }, delay);
    });
}

// Function với async/await
async function demoAsyncAwait() {
    console.log('🚀 Bắt đầu async function');
    
    console.log('1️⃣ Trước await đầu tiên');
    const ketQua1 = await taoPromise("Bước 1", 1000);
    console.log(`✅ Nhận được: ${ketQua1}`);
    
    console.log('2️⃣ Trước await thứ hai');
    const ketQua2 = await taoPromise("Bước 2", 800);
    console.log(`✅ Nhận được: ${ketQua2}`);
    
    console.log('3️⃣ Trước await thứ ba');
    const ketQua3 = await taoPromise("Bước 3", 600);
    console.log(`✅ Nhận được: ${ketQua3}`);
    
    console.log('🎉 Kết thúc async function');
    return `Hoàn thành với: ${ketQua1}, ${ketQua2}, ${ketQua3}`;
}

// Gọi function và quan sát
console.log('🎬 === BẮT ĐẦU DEMO ===');
console.log('📍 Trước khi gọi demoAsyncAwait()');

const promise = demoAsyncAwait();
console.log('📍 Sau khi gọi demoAsyncAwait() (không chặn!)');
console.log('💡 promise object:', promise);

promise.then(ketQua => {
    console.log('🏁 Kết quả cuối cùng:', ketQua);
});

console.log('📍 Code này chạy ngay lập tức, không bị chặn!');

// KẾT QUẢ IN RA:
// 🎬 === BẮT ĐẦU DEMO ===
// 📍 Trước khi gọi demoAsyncAwait()
// 🚀 Bắt đầu async function
// 1️⃣ Trước await đầu tiên
//    🟡 Bắt đầu Promise: "Bước 1" (1000ms)
// 📍 Sau khi gọi demoAsyncAwait() (không chặn!)
// 💡 promise object: Promise {}
// 📍 Code này chạy ngay lập tức, không bị chặn!
// (sau 1000ms)
//    🟢 Hoàn thành Promise: "Bước 1"
// ✅ Nhận được: Bước 1
// 2️⃣ Trước await thứ hai
//    🟡 Bắt đầu Promise: "Bước 2" (800ms)
// (sau 800ms)
//    🟢 Hoàn thành Promise: "Bước 2"
// ✅ Nhận được: Bước 2
// 3️⃣ Trước await thứ ba
//    🟡 Bắt đầu Promise: "Bước 3" (600ms)
// (sau 600ms)
//    🟢 Hoàn thành Promise: "Bước 3"
// ✅ Nhận được: Bước 3
// 🎉 Kết thúc async function
// 🏁 Kết quả cuối cùng: Hoàn thành với: Bước 1, Bước 2, Bước 3

🔄 So sánh: Callbacks vs Promises vs Async/Await

Cùng một logic với 3 cách tiếp cận khác nhau
JavaScript
// TÌNH HUỐNG: Lấy thông tin user → Lấy posts → Hiển thị

// ❌ CÁCH 1: CALLBACKS (Callback Hell)
function layThongTinVoiCallback(userId, callback) {
    layUser(userId, function(error, user) {
        if (error) {
            callback(error, null);
            return;
        }
        
        layPosts(user.id, function(error, posts) {
            if (error) {
                callback(error, null);
                return;
            }
            
            layComments(posts[0].id, function(error, comments) {
                if (error) {
                    callback(error, null);
                    return;
                }
                
                // Callback hell - lồng 3 tầng!
                callback(null, { user, posts, comments });
            });
        });
    });
}

// ✅ CÁCH 2: PROMISES (Promise Chaining)  
function layThongTinVoiPromiseFlat(userId) {
    let userData = {};
    
    return layUserPromise(userId)
        .then(user => {
            userData.user = user;
            return layPostsPromise(user.id);
        })
        .then(posts => {
            userData.posts = posts;
            return layCommentsPromise(posts[0].id);
        })
        .then(comments => {
            userData.comments = comments;
            return userData; // { user, posts, comments }
        })
        .catch(error => {
            console.error('Lỗi:', error);
            throw error;
        });
}

// 🚀 CÁCH 3: ASYNC/AWAIT (Hiện đại nhất)
async function layThongTinVoiAsyncAwait(userId) {
    try {
        // Code trông như đồng bộ, nhưng vẫn bất đồng bộ!
        const user = await layUserPromise(userId);
        const posts = await layPostsPromise(user.id);
        const comments = await layCommentsPromise(posts[0].id);
        
        return { user, posts, comments };
        
    } catch (error) {
        console.error('Lỗi:', error);
        throw error;
    }
}

// SO SÁNH ĐỘ DÀI:
// Callbacks:    ~15 dòng với nhiều lồng nhau
// Promises:     ~12 dòng với chaining
// Async/Await:  ~8 dòng, đọc như code bình thường

// SO SÁNH ERROR HANDLING:
// Callbacks:    Phải check error ở mỗi tầng
// Promises:     Một .catch() cuối cùng
// Async/Await:  try/catch như code đồng bộ

// SO SÁNH DEBUGGING:
// Callbacks:    Khó debug vì nhiều tầng
// Promises:     Khó đặt breakpoint trong .then()
// Async/Await:  Dễ debug như code bình thường

⚠️ Lưu ý quan trọng khi sử dụng Async/Await

  • Async function luôn trả về Promise: Dù bạn return string, nó sẽ thành Promise
  • Await chỉ dùng trong async function: Ngoài async function sẽ báo lỗi syntax
  • Vẫn là bất đồng bộ: Không chặn luồng chính, chỉ tạm dừng function hiện tại
  • Error handling: Dùng try/catch, hoặc .catch() khi gọi async function
  • Sequential vs Parallel: Cẩn thận khi muốn chạy song song

⚡ Sequential vs Parallel với Async/Await

🎯 Sequential vs Parallel trong Async/Await

Lưu ý quan trọng: Cả Sequential và Parallel đều là BẤT ĐỒNG BỘ! Sự khác biệt ở đây không phải sync vs async, mà là:

  • Sequential (Tuần tự): Các async operations chờ nhau - chạy lần lượt
  • Parallel (Song song): Các async operations chạy đồng thời - không chờ nhau

Một trong những lỗi phổ biến nhất là vô tình viết sequential khi các operations có thể chạy parallel để nhanh hơn.

Khía cạnh 🐌 Sequential (Tuần tự) 🚀 Parallel (Song song)
Bản chất Async operations chờ nhau Async operations chạy đồng thời
Cách thực thi Chờ task1 xong → Chờ task2 xong → Chờ task3 xong Khởi động tất cả cùng lúc → Chờ tất cả hoàn thành
Thời gian Tổng thời gian của tất cả tasks Thời gian của task chậm nhất
Cú pháp await task1(); await task2(); Promise.all([task1(), task2()])
Ưu điểm Đơn giản, dễ debug, task2 có thể dùng kết quả task1 Nhanh hơn, tối ưu performance
Nhược điểm Chậm, lãng phí thời gian chờ Phức tạp hơn, khó debug, tasks độc lập

⚠️ Đừng nhầm lẫn với Sync/Async!

Làm rõ khái niệm:

  • Sync vs Async: Chặn vs không chặn luồng chính (main thread)
  • Sequential vs Parallel: Chờ nhau vs chạy đồng thời (trong async operations)

Ví dụ: Cả sequential và parallel async đều KHÔNG chặn main thread, nhưng sequential chậm hơn vì chờ nhau!

❌ Lỗi phổ biến - Sequential không cần thiết:
// ❌ Async operations chờ nhau (sequential)
const user = await getUser();     // 2s, chờ xong
const posts = await getPosts();   // 1.5s, chờ xong  
const stats = await getStats();   // 1s, chờ xong
// Tổng: 4.5s - CHẬM!
// ✅ Async operations chạy đồng thời (parallel)
const [user, posts, stats] = await Promise.all([
    getUser(),    // Bắt đầu ngay
    getPosts(),   // Bắt đầu ngay  
    getStats()    // Bắt đầu ngay
]); // Tổng: 2s - NHANH!

🧠 Khi nào dùng Sequential, khi nào dùng Parallel?

Nhớ: Cả hai đều là async (không chặn main thread), chỉ khác về performance!

🐌 Dùng Sequential (chờ nhau) khi:
  • Operations phụ thuộc nhau: Operation 2 cần kết quả của Operation 1
  • Muốn kiểm soát thứ tự: Phải hoàn thành Operation 1 trước mới làm Operation 2
  • Server limitations: API không thể handle nhiều request cùng lúc
  • Error handling đặc biệt: Muốn dừng ngay khi có lỗi đầu tiên
🚀 Dùng Parallel (đồng thời) khi:
  • Operations độc lập: Không cần kết quả của nhau
  • Tối ưu performance: Muốn giảm thời gian tổng
  • Loading dashboard: Tải nhiều data source cùng lúc
  • Batch processing: Xử lý nhiều items giống nhau
Hiểu rõ Sequential vs Parallel execution
JavaScript
// Demo functions
async function task1() {
    console.log('🟡 Task 1 bắt đầu');
    await new Promise(resolve => setTimeout(resolve, 2000));
    console.log('🟢 Task 1 hoàn thành');
    return 'Kết quả 1';
}

async function task2() {
    console.log('🟡 Task 2 bắt đầu');
    await new Promise(resolve => setTimeout(resolve, 1500));
    console.log('🟢 Task 2 hoàn thành');
    return 'Kết quả 2';
}

async function task3() {
    console.log('🟡 Task 3 bắt đầu');
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log('🟢 Task 3 hoàn thành');
    return 'Kết quả 3';
}

// ❌ SEQUENTIAL - Chạy tuần tự (chậm)
async function chayTuanTu() {
    console.log('📚 === CHẠY TUẦN TỰ ===');
    const start = Date.now();
    
    const result1 = await task1(); // Chờ 2 giây
    const result2 = await task2(); // Chờ thêm 1.5 giây
    const result3 = await task3(); // Chờ thêm 1 giây
    // Tổng: ~4.5 giây
    
    const end = Date.now();
    console.log(`⏱️ Tổng thời gian: ${end - start}ms`);
    console.log('📋 Kết quả:', [result1, result2, result3]);
}

// ✅ PARALLEL - Chạy song song (nhanh)
async function chaySongSong() {
    console.log('🚀 === CHẠY SONG SONG ===');
    const start = Date.now();
    
    // Khởi động tất cả tasks cùng lúc (không await ngay)
    const promise1 = task1(); // Bắt đầu ngay
    const promise2 = task2(); // Bắt đầu ngay
    const promise3 = task3(); // Bắt đầu ngay
    
    // Chờ tất cả hoàn thành
    const results = await Promise.all([promise1, promise2, promise3]);
    // Tổng thời gian = task chậm nhất = 2 giây
    
    const end = Date.now();
    console.log(`⏱️ Tổng thời gian: ${end - start}ms`);
    console.log('📋 Kết quả:', results);
}

// ✅ HYBRID - Kết hợp linh hoạt
async function chayKetHop() {
    console.log('🎯 === CHẠY KẾT HỢP ===');
    const start = Date.now();
    
    // Bước 1: Chạy song song task1 và task2
    const [result1, result2] = await Promise.all([task1(), task2()]);
    
    // Bước 2: Chỉ chạy task3 sau khi có kết quả từ task1, task2
    const result3 = await task3();
    
    const end = Date.now();
    console.log(`⏱️ Tổng thời gian: ${end - start}ms`);
    console.log('📋 Kết quả:', [result1, result2, result3]);
}

// Test performance
async function testPerformance() {
    await chayTuanTu();     // ~4.5 giây
    console.log('');
    await chaySongSong();   // ~2 giây  
    console.log('');
    await chayKetHop();     // ~3 giây
}

testPerformance();

🎯 Tóm tắt Async/Await

  • Bản chất: Syntactic sugar cho Promises, không phải công nghệ mới
  • Cú pháp: async function + await promise
  • Hoạt động: Tạm dừng function tại await, không chặn luồng chính
  • Ưu điểm: Code dễ đọc như đồng bộ, dễ debug, error handling với try/catch
  • Lưu ý: Vẫn là bất đồng bộ, cẩn thận sequential vs parallel

🚨 Error Handling với Async/Await

Error Handling với Try/Catch - Comprehensive Guide
JavaScript
// 1. CƠ BẢN: Try/Catch với Async/Await

async function basicErrorHandling() {
    try {
        // Tất cả errors (Promise rejections) sẽ được catch ở đây
        const response = await fetch('/api/user/123');
        const user = await response.json();
        
        if (!response.ok) {
            throw new Error(`HTTP Error: ${response.status}`);
        }
        
        return user;
        
    } catch (error) {
        // Bắt TẤT CẢ errors từ bất kỳ await nào ở trên
        console.error('🚨 Lỗi:', error.message);
        
        // Có thể re-throw hoặc trả về default value
        throw error; // Re-throw để caller handle
        // return null; // Hoặc trả về default value
    }
}

// 2. MULTIPLE TRY/CATCH - Xử lý lỗi riêng biệt

async function multipleTryCatch(userId) {
    let user = null;
    let posts = [];
    let comments = [];
    
    // Bước 1: Lấy user info (critical - phải có)
    try {
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error('User not found');
        user = await response.json();
        console.log('✅ User loaded successfully');
    } catch (error) {
        console.error('❌ Failed to load user:', error.message);
        throw error; // User là critical, không thể tiếp tục
    }
    
    // Bước 2: Lấy posts (optional - có thể fail)
    try {
        const response = await fetch(`/api/users/${userId}/posts`);
        if (response.ok) {
            posts = await response.json();
            console.log(`✅ Loaded ${posts.length} posts`);
        }
    } catch (error) {
        console.warn('⚠️ Could not load posts:', error.message);
        // Không throw - posts không critical
    }
    
    // Bước 3: Lấy comments (optional)
    try {
        if (posts.length > 0) {
            const response = await fetch(`/api/posts/${posts[0].id}/comments`);
            if (response.ok) {
                comments = await response.json();
                console.log(`✅ Loaded ${comments.length} comments`);
            }
        }
    } catch (error) {
        console.warn('⚠️ Could not load comments:', error.message);
        // Không throw
    }
    
    return { user, posts, comments };
}

// 3. NESTED TRY/CATCH - Xử lý phức tạp

async function nestedErrorHandling() {
    try {
        // Outer try - bắt errors tổng quát
        const userData = await fetchUserData();
        
        try {
            // Inner try - specific error handling
            const enrichedData = await enrichUserData(userData);
            return enrichedData;
            
        } catch (enrichError) {
            console.warn('⚠️ Could not enrich data:', enrichError.message);
            // Fallback: trả về basic data
            return userData;
        }
        
    } catch (mainError) {
        console.error('❌ Critical error:', mainError.message);
        
        // Retry logic
        if (mainError.code === 'NETWORK_ERROR') {
            console.log('🔄 Retrying...');
            await new Promise(resolve => setTimeout(resolve, 1000));
            return nestedErrorHandling(); // Recursive retry
        }
        
        throw mainError;
    }
}

// 4. PROMISE.ALL() VỚI ERROR HANDLING

async function parallelWithErrorHandling() {
    try {
        // Tất cả promises phải thành công
        const [users, posts, categories] = await Promise.all([
            fetch('/api/users').then(r => r.json()),
            fetch('/api/posts').then(r => r.json()),
            fetch('/api/categories').then(r => r.json())
        ]);
        
        return { users, posts, categories };
        
    } catch (error) {
        console.error('❌ One or more requests failed:', error);
        
        // Fallback: load individually với error handling
        return await loadIndividually();
    }
}

async function loadIndividually() {
    const result = { users: [], posts: [], categories: [] };
    
    // Load từng cái một, bỏ qua errors
    try {
        const response = await fetch('/api/users');
        if (response.ok) result.users = await response.json();
    } catch (e) { console.warn('Could not load users'); }
    
    try {
        const response = await fetch('/api/posts');
        if (response.ok) result.posts = await response.json();
    } catch (e) { console.warn('Could not load posts'); }
    
    try {
        const response = await fetch('/api/categories');
        if (response.ok) result.categories = await response.json();
    } catch (e) { console.warn('Could not load categories'); }
    
    return result;
}

📝 Bài Tập Ôn Tập - Async/Await

🎯 Bài Tập 5: Chuyển đổi Promise chain thành Async/Await

Mục tiêu: Học cách refactor code từ Promise chaining sang Async/Await

Yêu cầu: Chuyển đổi function sử dụng Promise chain thành Async/Await

Bài Tập 5 - Refactor Promise chain
// Code gốc sử dụng Promise chain
function xuLyDonHangPromise(orderId) {
    return kiemTraTonKho(orderId)
        .then(sanPham => {
            if (sanPham.soLuong < 1) {
                throw new Error('Hết hàng!');
            }
            return tinhTien(sanPham);
        })
        .then(tongTien => thanhToan(tongTien))
        .then(ketQuaThanhToan => {
            if (ketQuaThanhToan.success) {
                return guiEmail(ketQuaThanhToan.orderId);
            } else {
                throw new Error('Thanh toán thất bại!');
            }
        })
        .then(() => console.log('✅ Đơn hàng hoàn tất!'))
        .catch(error => console.error('❌ Lỗi:', error.message));
}

// TODO: Chuyển đổi thành async/await
async function xuLyDonHangAsync(orderId) {
    // Viết code của bạn ở đây
}

// Mock functions để test
function kiemTraTonKho(orderId) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ id: orderId, ten: 'iPhone 15', soLuong: 5, gia: 25000000 });
        }, 1000);
    });
}

function tinhTien(sanPham) {
    return new Promise(resolve => {
        setTimeout(() => {
            const thue = sanPham.gia * 0.1;
            resolve(sanPham.gia + thue);
        }, 500);
    });
}

function thanhToan(tongTien) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ 
                success: true, 
                orderId: 'ORD-' + Date.now(),
                amount: tongTien 
            });
        }, 1500);
    });
}

function guiEmail(orderId) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`📧 Gửi email xác nhận cho đơn hàng ${orderId}`);
            resolve(true);
        }, 800);
    });
}

// Test
xuLyDonHangPromise('123');

🎯 Bài Tập 6: Xử lý nhiều async operations

Mục tiêu: Học cách sử dụng Promise.all() với async/await

Yêu cầu: Tạo dashboard load nhiều data nguồn cùng lúc

Bài Tập 6 - Dashboard với multiple async calls
// Tạo dashboard load dữ liệu từ nhiều nguồn
async function loadDashboard() {
    try {
        console.log('🔄 Đang tải dashboard...');
        
        // TODO: Sử dụng Promise.all để load song song:
        // - layThongTinUser()
        // - layThongKeBanHang() 
        // - layTinTuc()
        // - layThongBao()
        
        // Gợi ý: const [user, stats, news, notifications] = await Promise.all([...]);
        
        console.log('✅ Dashboard loaded thành công!');
        return { user, stats, news, notifications };
    } catch (error) {
        console.error('❌ Lỗi load dashboard:', error);
    }
}

// Mock API functions
async function layThongTinUser() {
    await new Promise(resolve => setTimeout(resolve, 1200));
    return { 
        name: 'Nguyễn Văn A', 
        role: 'Admin', 
        avatar: 'avatar.jpg' 
    };
}

async function layThongKeBanHang() {
    await new Promise(resolve => setTimeout(resolve, 800));
    return { 
        doanhThu: 150000000, 
        donHang: 234, 
        khachHang: 67 
    };
}

async function layTinTuc() {
    await new Promise(resolve => setTimeout(resolve, 1500));
    return [
        { title: 'Tin 1', content: 'Nội dung tin 1' },
        { title: 'Tin 2', content: 'Nội dung tin 2' }
    ];
}

async function layThongBao() {
    await new Promise(resolve => setTimeout(resolve, 600));
    return [
        { type: 'info', message: 'Hệ thống sẽ bảo trì lúc 2h sáng' },
        { type: 'warning', message: 'Còn 5 sản phẩm sắp hết hàng' }
    ];
}

// Test
loadDashboard();

📋 Tổng Kết Bài Học

✅ Những gì đã học được

🎉 Chúc mừng bạn đã hoàn thành bài học về JavaScript Async Programming!

💪 Tiếp tục luyện tập để thành thạo các patterns này trong dự án thực tế.