Toàn tập về Javascript Async Await – Tại sao lại nên dùng?

0

Javascript Async await được giới thiệu từ phiên bản NodeJS 7.6 và hiện tại thì nó được hỗ trợ trên tất cả các trình duyệt hiện đại.

Mình làm việc với Javascript cũng được một thời gian. Từ ngày mình cảm thây vui vẻ với callback, rồi sung sướng tột độ với Promise, cuối cùng thì vỡ òa với Async/await 🙂

Ngay từ cái tên gọi Javascript async await của nó cũng đã nói lên phần nào về tác dụng. Nó cũng giống với Promise hay callback về công dụng, tức là viết code không đồng bộ theo luồng logic đồng bộ.

Bài viết này chúng ta sẽ cùng nhau tìm hiểu tất cả những khía cạnh của Async/await, lý do tại sao chúng ta nên sử dụng nó thay vì Promise.

Javascript async await

Giới thiệu Async/await

Trước khi chúng ta bắt đầu vào tìm hiểu kỹ hơn, mình sẽ giới thiệu qua một số thông tin:

  • Async/await là một giải pháp mới để viết code không đồng bộ. Trước đây, chúng ta có hai giải pháp đó là dùng Callback và Promise.
  • Async/await thực sự chỉ là cách viết syntax được xây dựng từ promise. Nó không được sử dụng với plain callback hay node callback.
  • Async/await giống như promises, là non blocking.
  • Async/await làm cho đoạn mã không đồng bộ trông giống như mã đồng bộ. Đây chính là ưu điểm của nó so với callback và promise.

Cú pháp của Async/await

Giả sử, chúng ta có một hàm getJSON() trả về một promise, và promise này sẽ đưa kết quả là đối tượng JSON. Khi nhận được kết quả thì chúng chỉ log nó ra màn hình console và return là message “done”.

Nếu dùng Promise, chúng ta sẽ viết code như sau:

const makeRequest = () =>
  getJSON()
    .then(data => {
      console.log(data)
      return "done"
    })

makeRequest()

Còn nếu dùng Async/await thì nó sẽ “đẹp đẽ” như sau:

const makeRequest = async () => {
  console.log(await getJSON())
  return "done"
}

makeRequest()

Để mình phân tích điểm khác biệt giữa hai cách viết trên:

  • Chúng ta sử dụng từ khóa async trước một hàm. Từ khóa await chỉ được sử dụng bên trong một hàm được định nghĩa bằng khóa async. Bất kỳ hàm nào được khái báo với từ khóa async đều sẽ ngầm trả về một promise. Và Promise này sẽ resolve bất kỳ giá trị nào được trả về bằng từ khóa return (trong trường hợp trên thì promise sẽ resolve message “done”).
  • await getJSON() tức là hàm console.log() nó sẽ đợi cho đến khi hàm getJSON() lấy được kết quả.

Ok, giờ mình sẽ phân tích chi tiết hơn nhé.

>>Có ích này: Những lỗi sai Javascript khiến việc học trở thành “địa ngục”

Async

Như mình đã nói ở trên, hàm Async cho phép chúng ta viết code không đồng bộ mà nhìn như đồng bộ.  Nó hoạt động không đồng bộ thông qua event-loop.

Các hàm async luôn trả về một giá trị. Đó là một promise.

async function firstAsync() {
  return "VNTALKING.COM";
}

firstAsync().then(alert); // "VNTALKING.COM"

Bạn chạy đoạn mã trên nếu thấy có một alert với nội dung “VNTALKING.COM” thì tức là một promise đã được trả về. Nều không thì từ khóa then() không có ý nghĩa, nó không làm gì cả.

Await

Từ khóa Await được dùng để chờ một promise được trả về. Nó chỉ được sử dụng bên trong một đoạn code có Async.

Từ khóa Await sẽ báo cho Javascript chờ cho đến khi promise trả về một giá trị.

Lưu ý: Await chỉ làm cho khối có từ khóa Async phải chờ thôi nhé, chứ không phải là toàn bộ chương trình phải chờ.

Mình sẽ minh họa bằng đoạn code bên dưới đây:

async function firstAsync() {
    let promise = new Promise((res, rej) => {
        setTimeout(() => res("Now it's done!"), 1000)
    });

    // wait until the promise returns us a value
    let result = await promise; 
  
    // "Now it's done!"
    alert(result); 
    }
};firstAsync();

Một vài lưu ý khi sử dụng Async/Await

Dưới đây là một vài điểm mà các bạn cần lưu ý khi sử dụng Async/Await. Có những cái lỗi  mà bị vi phạm thì nó waring luôn nên cũng không quan ngại lắm. Ví dụ như cái số 1 bên dưới đây.

1. Await không thể viết bên ngoài Async.

Chúng ta không thể sử dụng await ở bên ngoài hàm được định nghĩa với async.

// Không thể viết như thế này.
// await makeRequest()

// Viết như này thì được.
makeRequest().then((result) => {
  // làm một cái gì đó ở đây.
})

2.  Không thể dùng Await bên trong một hàm thông thường.

Await bắt buộc phải đi kèm với Async. Vì vậy, nếu một hàm thông thường (không khai báo với từ khóa async) thì không sử dụng được Await.

function firstAsync() {
  let promise = Promise.resolve(10);
  let result = await promise; // Syntax error
}

Nếu muốn sử dụng Await thì thêm từ khóa async khi khai báo hàm. Như thế này nhé:

async function firstAsync() {
  let promise = Promise.resolve(10);
  let result = await promise; // Syntax error
}

3. Async Await làm cho code thực hiện tuần tự

Mặc dù không phải là điều xấu, nhưng có vẻ thực hiện các tác vụ đồng thời thì sẽ nhanh hơn.

Mình ví dụ:

async function sequence() {
  await promise1(50); // đợi 50ms…
  await promise2(50); // …sau đó đợi thêm 50ms.
  return "done!";
}

Đoạn code mất tổng cộng 100ms để hoàn thành. Điều này xảy ra là do đoạn code trên chạy theo đúng trình tự lần lượt từ trên xuống dưới. Hai promise được được trả lại, và mỗi promise mất 50ms để hoàn thành. Promise thứ 2 chỉ được thực hiện sau khi promise thứ nhất đã hoàn thành.

Đây không phải là cách làm tốt, nếu các tác vụ cần nhiều thời gian để hoàn thành, chúng ta cần phải thực hiện đồng thời.

Chúng ta có giải pháp để giải quyết bài toán này. Đó là sử dụng Promise.all()

Theo như mô tả của MDN:

The Promise.all() method returns a single Promise that resolves when all of the promises passed as an iterable have resolved or when the iterable contains no promises. It rejects with the reason of the first promise that rejects.MDN

Chúng ta sẽ chuyển đoạn code trên thành như sau:

async function sequence() {
    await Promise.all([promise1(), promise2()]);  
    return "done!";
}

Hàm Promise.all() được resolve khi tất cả các promise bên trong nó được resolve.

Lý do nên sử dụng Async/Await

Sau khi các bạn đã hiểu rõ hơn về Javascript Async Await thì có ai đặt câu hỏi là: Thế cái “của nợ” này tốt hơn Promise hay Callback chỗ nào? Nó vẫn thế mà!!!

Nếu ai hỏi như vậy thì mình thực sự thấy vui vì bạn đã rất chủ động tư duy. Để mình chỉ ra một vài ưu điểm nổi bật của Javascript Async Await, để xem có đáng sử dụng không nhé.

1.  Code ngắn gọn, dễ đọc

Rõ ràng là cách viết với async/await làm mã ngắn gọn hơn rất nhiều. Như ví dụ ở đầu bài viết, các bạn cũng thấy khá rõ. Có một cái hay ho là mặc dù viết mã ngắn gọn hơn nhưng nó lại không làm cho code trở nên khó hiểu, ngược lại, nó còn dễ đọc hơn.

Ngoài ra, việc dùng async/await cũng khắc phục được triệt để vấn đề callback hell, thậm chí cả promise hell.

2. Error handling

Async/Await giúp có thể xử lý cả lỗi đồng bộ và bất đồng bộ với cùng một cấu trúc try-catch.

Như trong ví dụ bên dưới đây với promise,  try/catch  sẽ không thể xử lý được với lỗi nếu JSON.parse bị failed, vì nó xảy ra bên trong promise. Chúng ta cần phải gọi thêm một .catch cùng với promise, duplicate đoạn mã xử lý lỗi.

const makeRequest = () => {
  try {
    getJSON()
      .then(result => {
        // this parse may fail
        const data = JSON.parse(result)
        console.log(data)
      })
      // uncomment this block to handle asynchronous errors
      // .catch((err) => {
      //   console.log(err)
      // })
  } catch (err) {
    console.log(err)
  }

Nào, bây giờ chúng sẽ viết lại đoạn mã trên với async/await

const makeRequest = async () => {
  try {
    // this parse may fail
    const data = JSON.parse(await getJSON())
    console.log(data)
  } catch (err) {
    console.log(err)
  }
}

3. Khắc phục triệt để điều kiện lồng nhau

Hãy thử tưởng tượng một bài toán mà cần phải thực hiện như bên dưới

  return getJSON()
    .then(data => {
      if (data.needsAnotherRequest) {
        return makeAnotherRequest(data)
          .then(moreData => {
            console.log(moreData)
            return moreData
          })
      } else {
        console.log(data)
        return data
      }
    })
}

Mặc dù với đoạn mã này không phải là callback hell, nhưng với điều kiện lồng nhau như vậy cũng đủ làm người đọc phải đau đầu.

Nếu dùng Async/await thì vấn đề sẽ được giải quyết:

const makeRequest = async () => {
  const data = await getJSON()
  if (data.needsAnotherRequest) {
    const moreData = await makeAnotherRequest(data);
    console.log(moreData)
    return moreData
  } else {
    console.log(data)
    return data    
  }
}

4. Dễ Debugging hơn

Dù lập trình bằng ngôn ngữ nào đi chăng nữa thì vấn đề debug luôn quan trọng.

Đây là một ưu điểm mà mình đánh giá rất cao. Nếu bạn dùng promise, khi debug, bạn sẽ gặp phải 2 vấn đề hơi nhức nhối:

  1. Bạn không để đặt breakpoint vào các arrow functions mà trả về một expressions (không có body).
  2. Nếu bạn đặt breakpoint bên trong một đoạn code .then(). Khi debug, bạn sử dụng phím tắt như step-over  thì nó sẽ không nhảy sang hàm .then() tiếp theo như  ý bạn.

Debug promise

Với Async/await thì bạn sẽ khắc phục được hay nhược điểm trên.

async-await-debug

Tạm kết

Javascript async await là một trong những tính năng tốt nhất được thêm vào Javascript. Nó giúp cho mã nguồn của bạn rõ ràng, sạch đẹp hơn rất nhiều.

Mình hi vọng rằng bạn sẽ học được một điều gì qua bài viết này. Ý kiến của bạn như thế nào? Để lại ngay dưới phần bình luận nhé.

Đọc thêm:

Dịch vụ phát triển ứng dụng mobile giá rẻ - chất lượng

Bình luận. Cùng nhau thảo luận nhé!

avatar
  Theo dõi bình luận  
Thông báo