Trong quá trình phát triển ứng dụng web, việc xử lý các tác vụ bất đồng bộ như gọi API, truy xuất cơ sở dữ liệu hay thực hiện các thao tác I/O luôn là một thách thức. Trước đây, các kỹ thuật như callback và Promise đã được sử dụng rộng rãi, nhưng đôi khi lại dẫn đến “callback hell” hoặc code khó đọc. Async/Await ra đời như một giải pháp hiện đại, giúp code bất đồng bộ trở nên trực quan và dễ bảo trì hơn.
1. Tổng quan về bất đồng bộ trong JavaScript
JavaScript là một ngôn ngữ lập trình đơn luồng, nghĩa là chỉ có thể thực thi một tác vụ tại một thời điểm. Để xử lý các tác vụ tốn thời gian mà không làm tắc nghẽn giao diện người dùng, JavaScript sử dụng cơ chế bất đồng bộ. Các kỹ thuật truyền thống như callback cho phép thực hiện các thao tác sau khi một tác vụ hoàn thành, nhưng nếu sử dụng không đúng cách, chúng có thể dẫn đến code rối rắm và khó theo dõi.
2. Async/Await là gì?
Async/Await là cú pháp được giới thiệu trong ECMAScript 2017, giúp làm việc với Promise trở nên đơn giản hơn. Hai từ khóa chính là:
- async: Được dùng để khai báo một hàm bất đồng bộ. Hàm này luôn trả về một Promise.
- await: Được sử dụng bên trong hàm async để tạm dừng việc thực thi của hàm cho đến khi Promise được giải quyết (resolve) hoặc bị từ chối (reject).
Nhờ vậy, bạn có thể viết code bất đồng bộ theo phong cách đồng bộ, dễ hiểu và dễ kiểm soát luồng xử lý.
3. Các hoạt động của Async/Await
3.1. Khai báo hàm Async
Để tạo một hàm bất đồng bộ, chỉ cần thêm từ khóa async trước định nghĩa hàm. Ví dụ:
async function fetchData() {
// Hàm này sẽ trả về một Promise
}3.2. Sử dụng Await
Bên trong hàm async, từ khóa await có thể được sử dụng để “chờ” kết quả của một Promise trước khi tiếp tục thực hiện các dòng code tiếp theo. Khi gặp await, JavaScript sẽ tạm dừng việc thực thi hàm cho đến khi Promise được giải quyết.
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
}3.3. Xử lý lỗi với Try/Catch
Một điểm mạnh của async/await là khả năng xử lý lỗi một cách trực quan thông qua khối try...catch. Điều này giúp code trở nên rõ ràng và dễ bảo trì hơn so với việc sử dụng .then().catch() với Promise.
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Lỗi khi gọi API');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Có lỗi xảy ra:', error);
}
}4. Ví dụ về Async/Await
Ví dụ 1: Gọi API giả lập
Giả sử bạn có một API trả về dữ liệu sau một khoảng thời gian, ta có thể mô phỏng bằng hàm setTimeout kết hợp với Promise:
function getUserData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: 'Nguyễn Văn A' };
resolve(data);
}, 2000);
});
}
async function displayUser() {
try {
console.log('Đang lấy dữ liệu người dùng...');
const user = await getUserData();
console.log('Dữ liệu người dùng:', user);
} catch (error) {
console.error('Lỗi khi lấy dữ liệu:', error);
}
}
displayUser();Trong ví dụ này, hàm getUserData trả về một Promise sau 2 giây. Hàm displayUser sử dụng async/await để “chờ” kết quả và hiển thị thông tin người dùng một cách mượt mà.
Ví dụ 2: Xử lý nhiều Promise liên tiếp
Bạn có thể dễ dàng kết hợp nhiều thao tác bất đồng bộ trong một hàm async mà không cần phải lồng nhau quá nhiều:
function fetchPost(postId) {
return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
.then(response => response.json());
}
function fetchComments(postId) {
return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}/comments`)
.then(response => response.json());
}
async function displayPostAndComments(postId) {
try {
const post = await fetchPost(postId);
console.log('Bài viết:', post);
const comments = await fetchComments(postId);
console.log('Bình luận:', comments);
} catch (error) {
console.error('Lỗi:', error);
}
}
displayPostAndComments(1);Ở đây, chúng ta gọi hai API liên tiếp để lấy thông tin bài viết và bình luận tương ứng. Cách sử dụng async/await giúp mã nguồn trở nên tuần tự và dễ hiểu.
5. Lợi ích khi sử dụng Async/Await
- Đơn giản hóa code bất đồng bộ: Async/await cho phép viết code theo cách tương tự như code đồng bộ, giúp giảm thiểu sự phức tạp khi xử lý nhiều tác vụ bất đồng bộ.
- Xử lý lỗi rõ ràng: Sử dụng try/catch giúp dễ dàng bắt và xử lý lỗi, từ đó cải thiện độ tin cậy của ứng dụng.
- Dễ bảo trì và mở rộng: Code được viết với async/await thường dễ đọc và dễ bảo trì hơn, đặc biệt trong các dự án lớn.
Kết luận
Async/Await đã trở thành một phần không thể thiếu trong bộ công cụ của nhà phát triển JavaScript hiện đại. Nhờ cú pháp trực quan và khả năng xử lý lỗi hiệu quả, async/await giúp code bất đồng bộ trở nên dễ hiểu, dễ bảo trì và giảm thiểu rủi ro phát sinh lỗi. Nếu bạn đang xây dựng các ứng dụng web phức tạp, hãy áp dụng async/await để cải thiện chất lượng code và tăng hiệu suất phát triển.
Hy vọng bài viết này đã cung cấp cho bạn cái nhìn tổng quan và đầy đủ về async/await trong JavaScript. Hãy theo dõi codetuthub.com để cập nhật thêm nhiều bài viết, hướng dẫn và mẹo hay về lập trình. Happy coding!
- Mời bạn đọc thêm: JavaScript ES6 là gì? Các phiên bản ES trong JavaScript









