Async/Await trong Javascript, tại sao chúng ta nên sử dụng.
Async/await là gì? Trước kia chúng ta phải sử dụng callback hoặc promises để làm việc với code bất đồng bộ trong javascript, trong nhiều trường hợp các callback, promises được viết lồng nhau điều này khiến việc đọc code, maintain, debug rất khó khăn. Trong ECMAScript 2016 (ES7) thì ...
Async/await là gì?
Trước kia chúng ta phải sử dụng callback hoặc promises để làm việc với code bất đồng bộ trong javascript, trong nhiều trường hợp các callback, promises được viết lồng nhau điều này khiến việc đọc code, maintain, debug rất khó khăn.
Trong ECMAScript 2016 (ES7) thì Async/await đã được bổ sung để việc viết code bất đồng bộ trong JavaScript tốt hơn:
- Async/await là một cách viết code bất đồng bộ mới mà trước đó ta dùng Promise và cũ hơn là callbacks.
- Async/await được build trên top promises. Async/await không thể được sử dụng được với callback.
- Async/await giống promises là Non-Blocking.
- Async/await giúp cho việc viết code bất đồng bộ trở nên trông giống với viết code đồng bộ hơn.
Cú pháp Async/await
Giả sử function getJSON trả về một promise, và promise đó resolve với một đối tượng JSON.
Code theo cách dùng Promise
const makeRequest = () =>
getJSON()
.then(data => {
console.log(data)
return "done"
})
makeRequest()
Và đây là cách thực hiện với async/await
const makeRequest = async () => {
console.log(await getJSON())
return "done"
}
Có một số khác biệt khi sử dụng async/await:
- Keyword async khai báo ở trước function và await bên trong function.
- Bắt buộc phải khai báo async thì bên trong function mới dùng được await.
- Với keyword await đặt trước function getJSON() nó sẽ đợi function này cho tới khi nào xong thì mới chạy tiếp xuống câu lệnh console.log(data).
Tại sao nên sử dụng Async/await ?
- Ngắn gọn và clean.
Ngay trong ví dụ trên, rõ ràng chúng ta đã tối giản được số dòng code khi sử dụng với async/await. Chúng ta đã không phải sử dụng .then, tạo function để xử lý response, hoặc khai báo biến data để lưu dữ liệu, chúng ta cũng tránh được việc phải sử dụng các block code lồng nhau.
- Xử lý lỗi.
Với Async/await chúng ta có thể xử lý cả lỗi đồng bộ và không đồng bộ với một cấu trúc cùng try/catch.
Trong ví dụ dưới đây sử dụng promises, try/catch sẽ không xử lý nếu JSON.parse thất bại vì nó đang xảy ra bên trong một promises. Để bắt được lỗi chúng ta cần gọi .catch cho promises lúc này code xử lý lỗi sẽ bị trùng lặp điều mà chúng ta không muốn.
const makeRequest = () => {
try {
// synchronous code
...
...
getJSON()
.then(result => {
// this parse may fail
const data = JSON.parse(result)
console.log(data)
})
// duplicate our error handling code
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err)
}
}
Bây giờ xử lý code trên cùng với async/await. Chỉ cần một block catch đã xử lý được lỗi nếu JSON.parse hoặc synchronous code thất bại.
const makeRequest = async () => {
try {
// synchronous code
...
...
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}
- Câu điều kiện
Hãy tưởng tượng chúng ta có một đoạn code dưới đây thực hiện request lấy về data sau đó dựa vào điều kiện để quyết định thực hiện một request khác.
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
}
})
}
Bạn có thể nhận thấy nesting code trên gồm nhiều block lồng nhau trông rất tạp khi sử dụng với điều kiện.
Chúng ta hãy viết lại nó cho dễ đọc với async/await.
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
}
}
- Giá trị trung gian
Có thể bạn đã gặp phải tình huống mà bạn gọi một promise1 và sau đó sử dụng những gì nó trả về để gọi promise2, sau đó sử dụng kết quả của cả hai promises để gọi promise3.
Code của bạn sẽ trông như thế này
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return promise2(value1)
.then(value2 => {
// do something
return promise3(value1, value2)
})
})
}
Chúng ta có thể viết ngắn lại một chút với Promise.all
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
// do something
return promise3(value1, value2)
})
}
Với cách sử dụng Promise.all đã rút ngắn được code, nhưng code trở nên khó hiểu hơn.
Cuối cùng viết lại code với async/await: chúng ta vừa có thể rút ngắn được code, vừa có thể dễ dàng hiểu được.
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}
- Debug và error stacks
Giả sử bạn cần gọi tới một chuỗi các promises như sau:
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
})
}
Nhược điểm là nếu lỗi xảy ra một trong những promise thì với cách này ta khó có thể biết nó xuất phát từ đâu, và khó hơn là không thể đặt debug vào một promise nào đó vì nó thực hiện trên một câu lệnh nối liền nhau.
Tuy nhiên với async/await có thể dễ dàng đặt debug vào bất cứ promise nào và hoàn toàn có thể biết lỗi xuất phát từ callPromisse() nào vì rõ ràng là chúng được viết tách rời ra theo từng action riêng rẽ.
const makeRequest = async () => {
breakpoint >> await callApromise())
await callApromise())
breakpoint >> await callApromise())
await callApromise())
}
Tóm lại
Async/await được cải tiến lên từ Promise trong phiên bản ES6 vẫn còn vài hạn chế, do đó bạn vẫn phải hiểu và nắm rõ cách tạo và dùng Promise nếu muốn chuyển sang refactor lại code với cách viết mới của function dùng async/await và đương nhiên async/await cũng mang lại ưu điểm tương đối lớn trong việc debug, handle error, và code ngắn gọn, dễ hiểu hơn nhiều.