Async/Await Trong Javascript Có Làm Khó Được Bạn?

Mỗi ngôn ngữ lập trình đều có những ưu và nhược điểm riêng. Trong số đó, Javascript là một ngôn ngữ lập trình phổ biến nhất hiện nay và đã trải qua một thời gian khó khăn khi phải sử dụng callbacks để xử lý code bất đồng bộ. Tuy nhiên, từ phiên bản ES7 trở đi, Javascript đã giới thiệu một giải pháp mới được lập trình viên rất ưa chuộng để giải quyết vấn đề bất đồng bộ đó là Async/Await.

Async/Await là gì?

Trước đây, để làm việc với code bất đồng bộ, chúng ta sử dụng callbackpromise. Tuy nhiên, Async/Await là cách viết code bất đồng bộ mới được Javascript giới thiệu từ phiên bản ES7 trở đi. Nó được xây dựng trên nền tảng Promise và tương thích với tất cả các Promise dựa trên API. Cụ thể:

Async được sử dụng để khai báo một hàm bất đồng bộ. Các hàm bất đồng bộ luôn trả về một giá trị. Việc sử dụng async chỉ đơn giản là thể hiện rằng một promise sẽ được trả về và nếu một promise không được trả về, Javascript sẽ tự động kết thúc nó.

Await được sử dụng để đợi một Promise. Nó chỉ có thể được sử dụng trong khối Async. Từ khóa Await khiến Javascript đợi cho đến khi promise trả về kết quả. Lưu ý rằng nó chỉ làm cho khối chức năng không đồng bộ chờ đợi chứ không phải cả chương trình thực thi.

Cú pháp Async/Await

1. Async

Từ khóa Async được đặt trước một hàm để làm cho hàm đó trả về một promise.

Ví dụ:

async function myFunction() { return "Hello"; }

Cách sử dụng tương tự như trên:

async function myFunction() { return Promise.resolve("Hello"); }

Dưới đây là cách sử dụng Promise:

myFunction().then(function(value) { /* code nếu thành công */ }, function(error) { /* code nếu có lỗi */ });

2. Await

Từ khóa Await được đặt trước một hàm để làm cho hàm đó chờ một promise.

let value = await promise;

Từ khóa await chỉ có thể được sử dụng trong một hàm không đồng bộ.

Ví dụ:

<!DOCTYPE html> <html> <body> <h2>JavaScript async / await</h2> <h1 id="demo"></h1> <script> async function myDisplay() { let myPromise = new Promise(function(myResolve, myReject) { myResolve("I love You !!"); }); document.getElementById("demo").innerHTML = await myPromise; } myDisplay(); </script> </body> </html>

Kết quả:

Những điều cần lưu ý khi sử dụng Async/Await

1. Không thể sử dụng Await trong các hàm thông thường

Ví dụ:

function firstAsync() { let promise = Promise.resolve(10); let result = await promise; // Lỗi cú pháp }

Để hàm trên hoạt động bình thường, chúng ta cần thêm từ khóa async trước function firstAsync();

2. Async/Await thực hiện tuần tự

Điều này không phải lúc nào cũng tốt, vì mã song song sẽ thực hiện nhanh hơn rất nhiều.

Ví dụ:

async function sequence() { await promise1(50); // Chờ 50ms… await promise2(50); // …rồi chờ thêm 50ms. return "done!"; }

Mã trên mất 100ms để hoàn thành, không phải là một khoảng thời gian lớn, nhưng vẫn khá chậm. Điều này xảy ra vì nó thực hiện tuần tự. Cả hai hàm đều trả về và mất 50ms để hoàn thành. Hàm thứ hai chỉ thực hiện sau khi hàm đầu tiên đã được giải quyết. Điều này không phải là một kỹ thuật tốt, vì các yêu cầu lớn có thể tốn rất nhiều thời gian. Chúng ta cần thực hiện song song.

Chúng ta có thể làm điều đó bằng cách sử dụng Promise.all()

Theo MDN: “Phương thức Promise.all(iterable) trả về một Promise mới và promise mới này chỉ được kết thúc khi tất cả các promise trong iterable đã được giải quyết hoặc có một promise bị lỗi. Kết quả của promise mới này là một mảng chứa kết quả của tất cả các promise theo đúng thứ tự hoặc kết quả lỗi của promise bị lỗi.”

Ví dụ:

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

Hàm Promise.all() sẽ chờ tất cả các promise bên trong được giải quyết và sau đó trả về kết quả.

Xử lí lỗi với Async/Await

Mặc định, khi sử dụng async/await, kết quả trả về là một promise đã được giải quyết. Nhưng nếu promise bị từ chối và xảy ra lỗi, chúng ta phải làm gì? Chúng ta có thể sử dụng try…catch để xử lí các lỗi này giống như đối với các hàm thông thường khác.

Ví dụ:

Nếu promise giải quyết thành công, sau đó await promise sẽ trả về kết quả. Nhưng trong trường hợp promise bị từ chối, nó sẽ ném ra một lỗi, tương tự như câu lệnh throw tại dòng đó.

async function f() { await Promise.reject(new Error("Whoops!")); }

Trong thực tế, promise có thể mất một thời gian trước khi bị từ chối. Trong trường hợp đó, sẽ có một khoảng thời gian chờ đợi trước khi await ném ra lỗi.

Chúng ta có thể bắt lỗi đó bằng cách sử dụng try…catch, giống như cách xử lí lỗi thông thường:

async function f() { try { let response = await fetch('http://no-such-url'); } catch(err) { alert(err); // TypeError: failed to fetch } } f();

Tại sao nên sử dụng Async/Await?

1. Ngắn gọn và dễ đọc

Ưu điểm đơn giản nhất của Async/Await chính là giảm số lượng code phải viết một cách đáng kể. Không cần phải sử dụng then và catch, chỉ viết code như viết code tuần tự thông thường, sau đó sử dụng try/catch để xử lí lỗi.

2. Xử lí lỗi

Async/Await giúp xử lí cả các lỗi đồng bộ và lỗi bất đồng bộ theo cùng một cú pháp. Không cần phải sử dụng cách xử lí riêng cho từng trường hợp. Với đoạn code sử dụng promise, không thể bắt được lỗi xảy ra bên trong promise nếu có lỗi JSON.parse. Ta phải sử dụng .catch bên trong promise và lặp lại đoạn code xử lí lỗi, điều này sẽ mất công đối với một đoạn code sản phẩm phức tạp.

const makeRequest = () => { try { getJSON().then(result => { // có thể xảy ra lỗi tại đây const data = JSON.parse(result) console.log(data) }) // mở block này để xử lí lỗi không đồng bộ // .catch((err) => { // console.log(err) // }) } catch (err) { console.log(err) }

Với Async/Await, cú pháp xử lí lỗi trở nên đơn giản hơn:

const makeRequest = async () => { try { // có thể xảy ra lỗi tại đây const data = JSON.parse(await getJSON()) console.log(data) } catch (err) { console.log(err) } }

3. Câu lệnh điều kiện

Hãy tưởng tượng một code giống như đoạn mã bên dưới, chúng ta cần tải một số dữ liệu và quyết định xem có nên trả về dữ liệu đó hoặc tải thêm chi tiết dựa trên một số giá trị trong dữ liệu.

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

Chỉ nhìn vào đoạn code trên thôi đã đủ làm bạn đau đầu. Rất dễ lạc trong các lời gọi hoạch định (6 cấp độ), dấu ngoặc nhọn và cách trả về là cần thiết để truyền kết quả cuối cùng từ hàm gốc.

Khi sử dụng Async/Await, đoạn code trở nên dễ đọc hơn:

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 } }

Promise và callback có thể khiến việc kết hợp câu lệnh điều kiện hoặc việc thực hiện lại mã không đồng bộ trở nên phức tạp và rối rắm. Nhưng với Async/Await, việc này dễ dàng hơn rất nhiều.

4. Debug

Khi làm việc với Async/Await, việc debug trở nên đơn giản hơn rất nhiều. Với Async/Await, bạn không cần phải sử dụng nhiều hàm mũi tên và bạn có thể gọi các cuộc gọi chờ mà không cần quan tâm đến tính bất đồng bộ của chúng.

Mỗi lần sử dụng await được tính là một dòng code, vì vậy bạn có thể đặt điểm dừng để debug từng dòng như thường.

Tổng kết

Đó là những điều cơ bản về Async/Await trong Javascript mà chúng ta đã tìm hiểu trong bài viết này. Nếu bạn thấy bài viết hữu ích, hãy đánh giá 5* và chia sẻ để mọi người tham khảo!

Hãy để lại bình luận để tôi có thể cải thiện hơn trong tương lai. Cảm ơn bạn!

Nguồn tham khảo:

  • W3SCHOOL
  • 6 Reasons Why JavaScript Async/Await Blows Promises Away

Related Posts