Hàm bất đồng bộ - Async Function
Hàm async
là một cú pháp đơn giản hơn của hàm Generator. Nó được đánh dấu bằng từ khóa async
và sử dụng lệnh await
để biểu thị các hoạt động bất đồng bộ.
So với Generator, hàm async
có những cải tiến sau đây:
- Bộ thực thi tích hợp: Việc thực thi hàm Generator phải dựa vào bộ thực thi, trong khi hàm
async
có bộ thực thi tích hợp sẵn, và cách gọi nó giống như gọi một hàm thông thường. - Ý nghĩa rõ ràng hơn:
async
vàawait
có ý nghĩa rõ ràng hơn so với dấu sao (*
) vàyield
.async
biểu thị rằng hàm có các hoạt động bất đồng bộ,await
biểu thị rằng biểu thức tiếp theo cần đợi kết quả. - Áp dụng rộng hơn: Module
co
quy định rằng sau lệnhyield
chỉ có thể là hàm Thunk hoặc đối tượng Promise, trong khi sau lệnhawait
của hàmasync
có thể là Promise và các giá trị nguyên thủy (Number, String và Boolean, nhưng sẽ tự động chuyển thành Promise với trạng tháifulfilled
ngay lập tức). - Giá trị trả về là Promise: Hàm
async
trả về một đối tượng Promise, điều này thuận tiện hơn so với hàm Generator trả về một đối tượng Iterator. Bạn có thể sử dụng phương thứcthen
để chỉ định các bước tiếp theo.
Một cách khái quát, hàm async
có thể coi là một tập hợp các hoạt động bất đồng bộ được đóng gói thành một đối tượng Promise, và lệnh await
chỉ là cú pháp đơn giản của lệnh then
bên trong.
So sánh giữa hàm async
và hàm Generator
Hàm async | Hàm Generator | |
---|---|---|
Cách khai báo | async function(){} | function* (){} |
Lệnh bất đồng bộ | await | yield |
Cú pháp cơ bản
Khai báo hàm bất đồng bộ
Bất kỳ hàm nào được khai báo với từ khóa async
đều tự động trả về một đối tượng Promise sau khi thực thi.
Hàm async
trả về một đối tượng Promise, và bạn có thể sử dụng phương thức then
để thêm các hàm gọi lại. Khi hàm được thực thi, nếu gặp lệnh await
, nó sẽ tạm dừng và đợi cho đến khi hoạt động bất đồng bộ hoàn thành, sau đó tiếp tục thực thi các câu lệnh trong thân hàm.
🌰 Ví dụ:
Câu lệnh bất đồng bộ trong hàm
Lệnh await
chỉ có thể được sử dụng trong hàm async
, không thể sử dụng độc lập.
Vì hàm async
trả về một đối tượng Promise, nên nó có thể được sử dụng làm đối số cho lệnh await
.
Giá trị trả về của câu lệnh bất đồng bộ
Lệnh await
phải được theo sau bởi một Promise.
Một trong các chức năng của lệnh await
là nhận giá trị được truyền từ trạng thái thành công của đối tượng Promise.
Lệnh await
chỉ có thể được sử dụng trong hàm async
, nếu không sẽ gây ra lỗi.
Cú pháp
Kiểu giá trị trả về
Hàm async
trả về một đối tượng Promise.
Giá trị được trả về bởi lệnh return
trong hàm async
sẽ trở thành đối số của hàm gọi lại của phương thức then
.
Nếu hàm async
ném ra một ngoại lệ, đối tượng Promise trả về sẽ có trạng thái bị từ chối (rejected
). Lỗi được ném ra sẽ được nhận bởi hàm gọi lại của phương thức catch
.
Thay đổi trạng thái giá trị trả về
Đối tượng Promise trả về từ hàm async
chỉ thay đổi trạng thái sau khi tất cả các đối tượng Promise sau lệnh await
trong hàm đã thực thi xong, trừ khi gặp lệnh return
hoặc ném ra lỗi. Điều này có nghĩa là chỉ khi tất cả các hoạt động bất đồng bộ trong hàm async
đã hoàn thành, phương thức then
được chỉ định mới được thực thi.
🌰 Ví dụ:
Trong đoạn mã trên, hàm foo
có ba hàm trễ. Chỉ khi ba hoạt động này đã hoàn thành theo thứ tự, phương thức console.log
trong phương thức then
mới được thực thi.
Giá trị trả về của câu lệnh bất đồng bộ
Thường thì lệnh await
sẽ được theo sau bởi một đối tượng Promise và trả về kết quả của đối tượng Promise đó. Nếu không phải là một đối tượng Promise, giá trị tương ứng sẽ được trả về trực tiếp.
🌰 Ví dụ:
Xử lý ngoại lệ
Bắt ngoại lệ
Nếu một đối tượng Promise sau lệnh await
trở thành trạng thái rejected
, thì toàn bộ hàm async
sẽ dừng lại.
🌰 Ví dụ:
Khi một hàm async
có một câu lệnh await
trả về một Promise ở trạng thái rejected
, các câu lệnh await
sau đó sẽ không được thực thi.
Giải pháp: Sử dụng câu lệnh try-catch hoặc sử dụng phương thức catch
trên Promise trả về từ câu lệnh await
để bắt lỗi.
Đôi khi, chúng ta muốn tiếp tục thực hiện các hoạt động bất đồng bộ sau khi một hoạt động bất đồng bộ trước đó thất bại. Trong trường hợp này, bạn có thể đặt câu lệnh await
đầu tiên trong một khối try…catch
để đảm bảo rằng dù hoạt động bất đồng bộ đó thành công hay không, câu lệnh await
thứ hai vẫn được thực thi.
🌰 Ví dụ:
Một phương pháp khác là gắn thêm phương thức catch
vào đối tượng Promise trả về từ câu lệnh await
, để xử lý lỗi có thể xảy ra trước đó.
🌰 Ví dụ:
Gián đoạn hoạt động
Nếu hoạt động bất đồng bộ sau lệnh await
gặp lỗi, thì hàm async
sẽ trả về một Promise bị từ chối (rejected
).
Sử dụng câu lệnh try…catch
để thực hiện nhiều lần thử lại.
🌰 Ví dụ:
Nếu hoạt động await
thành công, sẽ sử dụng câu lệnh break
để thoát khỏi vòng lặp; nếu thất bại, nó sẽ được bắt bởi câu lệnh catch
, sau đó tiếp tục vòng lặp tiếp theo.
Nguyên lý triển khai
Cách thức triển của hàm async
là đóng gói hàm Generator và trình thực thi tự động vào trong một hàm.
🌰 Ví dụ:
Tương đương với:
Tất cả các hàm async
có thể được viết dưới dạng thứ hai như trên, trong đó spawn
là trình thực thi tự động.
Thực hành tốt nhất
Chặn bất đồng bộ
Các yêu cầu sau luôn phụ thuộc vào dữ liệu được trả về từ yêu cầu trước đó.
🌰 Ví dụ:
Bất đồng bộ không chặn
Trong một số tình huống kinh doanh, bạn có thể cần xử lý nhiều bước liên tiếp, nhưng các bước này không nhất thiết phụ thuộc vào nhau. Do đó, bạn có thể tối ưu hóa các hoạt động này.
Kết hợp giữa Bất đồng bộ chặn và Bất đồng bộ không chặn, chúng ta có thể tận dụng Event Loop và thực hiện các hàm bất đồng bộ này đồng thời.
🌰 Ví dụ:
Thêm một cách viết tương tự và tinh tế hơn.
Bất đồng bộ đồng thời
Các yêu cầu mạng đồng thời không phụ thuộc vào nhau, tốt nhất là sử dụng phương thức Promise.all
để cùng lúc gửi các yêu cầu.
🌰 Ví dụ:
Cả hai cách viết trên đều cho phép getUserList
và getOrderList
được gửi cùng một lúc, giúp rút ngắn thời gian thực thi chương trình.
Bất đồng bộ đa luồng không xác định
Tiếp tục từ phương pháp thực hành trước, khi chúng ta cần giải quyết một số lượng không xác định các Promise, chúng ta chỉ cần tạo một mảng và lưu trữ chúng, sau đó sử dụng phương thức Promise.all
để đợi đồng thời tất cả các Promise trả về kết quả.
🌰 Ví dụ:
Vòng lặp bất đồng bộ không chờ kết quả
await
cho mỗi tác vụ lặp lại, lưu ý rằng hàm vô danh được thực thi trong vòng lặp cũng phải được đặt là một hàm bất đồng bộ async
.
Lặp tuần tự bất đồng bộ
Để đợi tất cả các kết quả trả về, chúng ta phải quay lại cách viết truyền thống với vòng lặp for
:
Vòng lặp trên được thực hiện tuần tự, chúng ta cũng có thể chuyển đổi nó thành đồng thời.
Lặp đồng thời bất đồng bộ
Chúng ta có thể thay đổi mã trên để thực hiện các hoạt động bất đồng bộ đồng thời: