Lập Trình Bất Đồng Bộ - Callbacks, Promises, Async/Await
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.
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ư:
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!
// ❌ 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
JavaScript sử dụng Event Loop (Vòng lặp sự kiện) để xử lý các tác vụ 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
// 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();
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 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ãy tưởng tượng bạn gọi điện đặt pizza:
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
// Đị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.
// 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}`);
});
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:
// ❌ 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
// ❌ 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
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:
(error, userData)// 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
});
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:
// 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");
Sau khi hoàn thành bài tập 2, hãy chú ý:
Hãy tưởng tượng bạn hẹn bạn thân đi xem phim:
Trong lập trình, Promise hoạt động tương tự!
| 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... |
// 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 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:
// ✅ 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
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 failVD: 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ấtVD: { server: 'A', data: '...' } 💥 Winner thất bại: Error của promise nhanh nhấtVD: 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:[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. |
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 |
.then(results) nhận mảng kết quả [value1, value2, ...] theo đúng thứ tự.catch(error) nhận lỗi của promise đầu tiên bị reject, KHÔNG có kết quả nào cả// 🎯 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
`);
.then(result) nhận giá trị của promise đó.catch(error) nhận lỗi của promise đó// 🏁 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));
{ status: 'fulfilled', value: ... } hoặc { status: 'rejected', reason: ... }.{ status: 'fulfilled', value: actualResult }{ status: 'rejected', reason: errorObject }// 🎯 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');
});
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
// 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));
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: 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();
Async/Await thực chất là "syntactic sugar" (cú pháp ngọt ngào) cho Promises:
// 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"
}
// 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
// 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: 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à:
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 |
Làm rõ khái niệm:
Ví dụ: Cả sequential và parallel async đều KHÔNG chặn main thread, nhưng sequential chậm hơn vì chờ nhau!
// ❌ 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!
Nhớ: Cả hai đều là async (không chặn main thread), chỉ khác về performance!
// 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();
async function + await promiseawait, không chặn luồng chính// 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;
}
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
// 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');
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
// 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();
🎉 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ế.