async/await trong JavaScript là gì?

Từ khóa async được sử dụng để khai báo một hàm bất đồng bộ (asynchronous function). Một hàm được định nghĩa với từ khóa async sẽ luôn trả về một Promise, và nó cho phép sử dụng cú pháp await để xử lý các tác vụ bất đồng bộ một cách tuần tự, dễ đọc và rõ ràng hơn.

Đặc điểm của hàm async:

  1. Trả về một Promise:
    • Nếu trong hàm async có trả về một giá trị, giá trị đó sẽ được tự động bọc trong một Promise.
    • Nếu không trả về gì, hàm sẽ trả về Promise<void>.
async function example() {
    return "Hello, async!";
}

example().then((message) => console.log(message)); // "Hello, async!"
  1. Kết hợp với await:
    • await chỉ được sử dụng bên trong hàm async.
    • Nó sẽ tạm dừng việc thực thi của hàm async cho đến khi Promise được giải quyết (hoàn thành) và tiếp tục với kết quả của Promise.
async function fetchData() {
    console.log("Start fetching...");

    const data = await new Promise((resolve) =>
        setTimeout(() => resolve("Dữ liệu nhận được!"), 2000)
    );

    console.log(data);
    console.log("Done fetching!");
}

fetchData();
Start fetching...
(Dừng trong 2 giây)
Dữ liệu nhận được!
Done fetching!

Các tình huống sử dụng async/await:

1. Xử lý tác vụ bất đồng bộ một cách tuần tự:

Khi bạn cần thực thi nhiều tác vụ bất đồng bộ lần lượt, async/await giúp tránh Promise chaining.

async function processTasks() {
    const task1 = await new Promise((resolve) => setTimeout(() => resolve("Task 1 hoàn thành!"), 1000));
    console.log(task1);

    const task2 = await new Promise((resolve) => setTimeout(() => resolve("Task 2 hoàn thành!"), 2000));
    console.log(task2);

    console.log("Tất cả tác vụ hoàn thành!");
}

processTasks();
[code]
Task 1 hoàn thành!
(Dừng 1 giây)
Task 2 hoàn thành!
(Dừng 2 giây)
Tất cả tác vụ hoàn thành!

2. Xử lý lỗi với try...catch:

Bạn có thể dễ dàng quản lý lỗi khi sử dụng async/await thông qua try...catch.

async function fetchDataWithError() {
    try {
        const response = await new Promise((_, reject) => reject("Có lỗi xảy ra!"));
        console.log(response);
    } catch (error) {
        console.error("Lỗi:", error);
    }
}

fetchDataWithError();

 

Lỗi: Có lỗi xảy ra!

3. Chạy tác vụ song song với Promise.all():

Nếu không cần chờ tuần tự, bạn có thể chạy các tác vụ bất đồng bộ song song để tiết kiệm thời gian.

async function parallelTasks() {
    const [result1, result2] = await Promise.all([
        new Promise((resolve) => setTimeout(() => resolve("Task A xong!"), 2000)),
        new Promise((resolve) => setTimeout(() => resolve("Task B xong!"), 1000))
    ]);

    console.log(result1); // Task A xong!
    console.log(result2); // Task B xong!
}

parallelTasks();
Task B xong!
Task A xong!

 

Lưu ý quan trọng:

  1. await chỉ dùng trong hàm async:
    • Nếu bạn sử dụng await bên ngoài một hàm async, nó sẽ gây lỗi.

    Ví dụ:

    // Sẽ gây lỗi!
    const data = await fetch('https://api.example.com');
    
    
  2. Chạy đồng thời thay vì tuần tự khi không cần thiết:
    • Không sử dụng await tuần tự nếu các tác vụ không phụ thuộc lẫn nhau. Thay vào đó, sử dụng Promise.all().
  3. Lỗi không được bắt (Unhandled Rejection):
    • Đảm bảo bạn luôn xử lý lỗi từ Promise bằng try...catch hoặc .catch().

Ưu điểm của async/await:

  1. Cú pháp dễ đọc, giống mã đồng bộ, giúp giảm độ phức tạp.
  2. Dễ dàng xử lý lỗi với try...catch.
  3. Loại bỏ vấn đề “callback hell”.

Nhược điểm:

  1. Mặc dù cú pháp dễ đọc, việc sử dụng await tuần tự có thể làm giảm hiệu suất nếu không cần thiết.
  2. Người mới học dễ nhầm lẫn giữa bất đồng bộ và đồng bộ.