Promises trong JavaScript: Xử Lý Bất Đồng Bộ Hiệu Quả

Promise là gì?

Promise là một object đại diện cho kết quả (thành công hoặc thất bại) của một thao tác bất đồng bộ trong tương lai. Promise giúp code JavaScript dễ đọc và dễ quản lý hơn so với callback truyền thống.

Vấn đề với Callback Hell

Trước khi có Promise, chúng ta thường gặp “Callback Hell”:

getUser(userId, function(user) {
    getOrders(user.id, function(orders) {
        getOrderDetails(orders[0].id, function(details) {
            console.log(details);
            // Càng lúc càng lồng sâu...
        });
    });
});

Cú pháp Promise

Tạo Promise

const myPromise = new Promise((resolve, reject) => {
    // Thực hiện tác vụ bất đồng bộ
    const success = true;
    
    if (success) {
        resolve('Thành công!');
    } else {
        reject('Có lỗi xảy ra!');
    }
});

3 trạng thái của Promise

  1. Pending: Đang chờ xử lý
  2. Fulfilled: Hoàn thành thành công (resolve được gọi)
  3. Rejected: Thất bại (reject được gọi)

Sử dụng Promise

then() và catch()

myPromise
    .then(result => {
        console.log(result); // "Thành công!"
    })
    .catch(error => {
        console.error(error);
    });

Ví dụ thực tế: Fetch API

fetch('https://api.example.com/users')
    .then(response => response.json())
    .then(users => {
        console.log('Danh sách users:', users);
    })
    .catch(error => {
        console.error('Lỗi:', error);
    });

Promise Chaining

Promise có thể chain (nối tiếp) để thực hiện nhiều tác vụ tuần tự:

getUser(userId)
    .then(user => {
        console.log('User:', user.name);
        return getOrders(user.id);
    })
    .then(orders => {
        console.log('Orders:', orders);
        return getOrderDetails(orders[0].id);
    })
    .then(details => {
        console.log('Order Details:', details);
    })
    .catch(error => {
        console.error('Error:', error);
    });

Lưu ý: Phải return Promise trong .then() để chain tiếp.

Các phương thức Promise tĩnh

Promise.all()

Chờ tất cả promises hoàn thành:

const promise1 = fetch('/api/users');
const promise2 = fetch('/api/products');
const promise3 = fetch('/api/orders');

Promise.all([promise1, promise2, promise3])
    .then(([users, products, orders]) => {
        console.log('Tất cả đã load xong!');
    })
    .catch(error => {
        console.error('Có ít nhất 1 promise lỗi:', error);
    });

Lưu ý: Nếu 1 promise reject, toàn bộ Promise.all() sẽ reject.

Promise.allSettled()

Chờ tất cả promises hoàn thành (dù thành công hay thất bại):

const promises = [
    fetch('/api/users'),
    fetch('/api/invalid-endpoint'),
    fetch('/api/products')
];

Promise.allSettled(promises)
    .then(results => {
        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                console.log(`Promise ${index} thành công:`, result.value);
            } else {
                console.log(`Promise ${index} thất bại:`, result.reason);
            }
        });
    });

Promise.race()

Trả về kết quả của promise hoàn thành đầu tiên:

const promise1 = new Promise(resolve => setTimeout(() => resolve('Chậm'), 2000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('Nhanh'), 500));

Promise.race([promise1, promise2])
    .then(result => {
        console.log(result); // "Nhanh"
    });

Use case: Timeout cho requests:

const fetchWithTimeout = (url, timeout = 5000) => {
    return Promise.race([
        fetch(url),
        new Promise((_, reject) => 
            setTimeout(() => reject(new Error('Timeout!')), timeout)
        )
    ]);
};

Promise.any()

Trả về promise thành công đầu tiên:

const promise1 = Promise.reject('Lỗi 1');
const promise2 = new Promise(resolve => setTimeout(() => resolve('OK'), 1000));
const promise3 = new Promise(resolve => setTimeout(() => resolve('OK 2'), 2000));

Promise.any([promise1, promise2, promise3])
    .then(result => {
        console.log(result); // "OK"
    });

Xử lý lỗi nâng cao

finally()

Luôn chạy dù Promise thành công hay thất bại:

fetch('/api/data')
    .then(response => response.json())
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.error(error);
    })
    .finally(() => {
        console.log('Cleanup code - luôn chạy');
        hideLoadingSpinner();
    });

Xử lý lỗi trong chain

getUserData(userId)
    .then(user => {
        if (!user.isActive) {
            throw new Error('User không active');
        }
        return user;
    })
    .then(user => getOrders(user.id))
    .catch(error => {
        console.error('Lỗi:', error.message);
        // Có thể return giá trị mặc định
        return [];
    })
    .then(orders => {
        console.log('Orders:', orders);
    });

Ví dụ thực tế

1. API Call với error handling

function fetchUserData(userId) {
    return fetch(`/api/users/${userId}`)
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            console.log('User data:', data);
            return data;
        })
        .catch(error => {
            console.error('Fetch error:', error);
            throw error;
        });
}

2. Load nhiều resources song song

async function loadDashboard() {
    const [users, products, stats] = await Promise.all([
        fetch('/api/users').then(r => r.json()),
        fetch('/api/products').then(r => r.json()),
        fetch('/api/stats').then(r => r.json())
    ]);
    
    return { users, products, stats };
}

3. Retry logic

function fetchWithRetry(url, retries = 3) {
    return fetch(url)
        .catch(error => {
            if (retries > 0) {
                console.log(`Retry... (${retries} lần còn lại)`);
                return fetchWithRetry(url, retries - 1);
            }
            throw error;
        });
}

Promise vs Async/Await

Promise và async/await thường được dùng cùng nhau:

// Với Promise
function getUser() {
    return fetch('/api/user')
        .then(r => r.json())
        .then(user => {
            return fetch(`/api/orders/${user.id}`);
        })
        .then(r => r.json());
}

// Với Async/Await (dễ đọc hơn)
async function getUser() {
    const userResponse = await fetch('/api/user');
    const user = await userResponse.json();
    
    const ordersResponse = await fetch(`/api/orders/${user.id}`);
    return ordersResponse.json();
}

Best Practices

  1. Luôn có .catch() để xử lý lỗi
  2. Return promises trong .then() khi chain
  3. Sử dụng finally() cho cleanup code
  4. Dùng Promise.all() để chạy song song thay vì tuần tự
  5. Cân nhắc async/await cho code dễ đọc hơn
  6. Tránh nested promises - dùng chaining thay thế

Kết luận

Promise là nền tảng của lập trình bất đồng bộ trong JavaScript hiện đại. Hiểu rõ Promise sẽ giúp bạn:

  • Viết code bất đồng bộ rõ ràng hơn
  • Xử lý lỗi tốt hơn
  • Kết hợp tốt với async/await
  • Tránh callback hell

Hãy thực hành và làm quen với Promise để trở thành một JavaScript developer giỏi hơn!