Có một cú pháp đặc biệt để làm việc với những lời hứa theo cách thoải mái hơn, được gọi là async / await. Thật đáng ngạc nhiên dễ hiểu và sử dụng.
Nội dung chính
1. Hàm không đồng bộ
Hãy bắt đầu với async
từ khóa. Nó có thể được đặt trước một hàm, như thế này:
async function f() {
return 1;
}
Từ “async” trước một hàm có nghĩa là một điều đơn giản: một hàm luôn trả về một lời hứa. Các giá trị khác được gói trong một lời hứa được giải quyết tự động.
Chẳng hạn, hàm này trả về một lời hứa đã được giải quyết với kết quả là 1
; Hãy kiểm tra nó:
async function f() {
return 1;
}
f().then(alert); // 1
Chúng ta hoàn toàn có thể trả lại một lời hứa, điều này sẽ giống nhau:
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
Vì vậy, async
đảm bảo rằng hàm trả về một lời hứa và kết thúc các lời hứa trong đó. Đủ đơn giản, phải không? Nhưng không chỉ có thế. Có một từ khóa khác, await
chỉ hoạt động bên trong các async
hàm , và nó khá tuyệt.
2. Await
Cú pháp:
// works only inside async functions
let value = await promise;
Từ khóa await
làm cho JavaScript chờ cho đến khi lời hứa đó làm xong và trả về kết quả của nó.
Đây là một ví dụ với lời hứa sẽ giải quyết trong 1 giây:
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // wait until the promise resolves (*)
alert(result); // "done!"
}
f();
Việc thực thi hàm đã tạm dừng các dòng tại dòng (*)
và tiếp tục lại khi lời hứa đã ổn định, và result
trở thành kết quả của nó. Vì vậy, đoạn code trên cho thấy đã thực hiện! trong một giây
Chúng ta hãy nhấn mạnh: await
nghĩa đen là làm cho JavaScript chờ cho đến khi lời hứa được giải quyết, rồi tiếp tục với kết quả. Điều đó không tốn bất kỳ tài nguyên CPU nào, bởi vì công cụ có thể thực hiện các công việc khác trong thời gian này: thực thi các tập lệnh khác, xử lý các sự kiện, v.v.
Đó chỉ là một cú pháp tao nhã để nhận được kết quả hứa hẹn hơn promise.then
, dễ đọc và viết hơn.
Không thể sử dụng await
trong các chức năng thông thường
Nếu chúng ta cố gắng sử dụng hàmawait
không đồng bộ, sẽ có lỗi cú pháp:
function f() {
let promise = Promise.resolve(1);
let result = await promise; // Syntax error
}
Chúng ta sẽ nhận được lỗi này nếu chúng ta không đặt async
trước một hàm. Như đã nói, await
chỉ hoạt động bên trong một async function
.
Hãy lấy showAvatar()
ví dụ từ chương Một chuỗi Hứa hẹn và viết lại bằng cách sử dụng async/await
:
- Chúng ta sẽ cần phải thay thế các cuộc gọi
.then
bằngawait
. - Ngoài ra chúng ta nên làm cho hàm là
async
để nó làm việc.
async function showAvatar() {
// read our JSON
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// read github user
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// show the avatar
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// wait 3 seconds
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
Khá sạch sẽ và dễ đọc, phải không? Tốt hơn nhiều so với trước đây.await
sẽ không làm việc trực tiếp với code bất đồng bộ
Những người mới bắt đầu sử dụng await
có xu hướng quên mất thực tế là chúng ta không thể sử dụng codeawait
với code bất đồ bộ mà cần phải tham qua một hàm . Ví dụ: điều này sẽ không hoạt động:
// syntax error in top-level code
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
Nhưng chúng ta có thể gói nó thành một hàm async ẩn danh, như thế này:
(async () => {
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
...
})();
chấp nhận await
Giống như promise.then
, await
cho phép chúng ta sử dụng các đối tượng có thể đọc được (những đối tượng có phương thứcthen
có thể gọi được ). Ý tưởng là một đối tượng bên thứ ba có thể không phải là một lời hứa, nhưng tương thích với lời hứa: nếu nó hỗ trợ .then
, điều đó đủ để sử dụng nó với await
.
Đây là một lớpThenable
demo ; các await
bên dưới chấp nhận trường hợp của nó:
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve);
// resolve with this.num*2 after 1000ms
setTimeout(() => resolve(this.num * 2), 1000); // (*)
}
};
async function f() {
// waits for 1 second, then result becomes 2
let result = await new Thenable(1);
alert(result);
}
f();
Nếu await
nhận được một đối tượng không hứa hẹn .then
, nó gọi phương thức đó cung cấp các hàm dựng sẵn resolve
và reject
làm đối số (giống như đối với Promise
thông thường ). Sau đó await
đợi cho đến khi một trong số chúng được gọi (trong ví dụ trên nó xảy ra trong dòng (*)
) và sau đó tiến hành kết quả.
Phương thức lớp không đồng bộ
Để khai báo một phương thức lớp async, chỉ cần thêm nó vào async
:
class Waiter {
async wait() {
return await Promise.resolve(1);
}
}
new Waiter()
.wait()
.then(alert); // 1
Ý nghĩa là như nhau: nó đảm bảo rằng giá trị trả về là một lời hứa và cho phép await
.
3. Xử lý lỗi
Nếu một lời hứa giải quyết bình thường, sau đó await promise
trả về kết quả. Nhưng trong trường hợp từ chối, nó sẽ ném lỗi, giống như có một câu lệnh throw
tại dòng đó.
Code này:
async function f() {
await Promise.reject(new Error("Whoops!"));
}
… cũng giống như thế này:
async function f() {
throw new Error("Whoops!");
}
Trong tình huống thực tế, lời hứa có thể mất một thời gian trước khi nó từ chối. Trong trường hợp đó sẽ có độ trễ trước khi await
đưa 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 thông thường throw
:
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
alert(err); // TypeError: failed to fetch
}
}
f();
Trong trường hợp có lỗi, điều khiển nhảy vào khốicatch
. Chúng ta cũng có thể gói nhiều dòng:
async function f() {
try {
let response = await fetch('/no-user-here');
let user = await response.json();
} catch(err) {
// catches errors both in fetch and response.json
alert(err);
}
}
f();
Nếu chúng ta không có try..catch
, thì lời hứa được tạo bởi lệnh gọi của hàm async f()
sẽ bị từ chối. Chúng tôi có thể chắp thêm .catch
để xử lý nó:
async function f() {
let response = await fetch('http://no-such-url');
}
// f() becomes a rejected promise
f().catch(alert); // TypeError: failed to fetch // (*)
Nếu chúng ta quên thêm vào .catch
đó, thì chúng ta sẽ gặp lỗi lời hứa chưa được xử lý (có thể xem trong console). Chúng ta có thể bắt lỗi như vậy bằng cách sử dụng trình xử lýunhandledrejection
sự kiện toàn cầu như được mô tả trong chương Xử lý lỗi với các lời hứa.async/await
và promise.then/catch
Khi chúng ta sử dụng async/await
, chúng ta hiếm khi cần .then
, bởi vì await
xử lý sự chờ đợi cho chúng ta. Và chúng ta có thể sử dụng thường xuyên try..catch
thay vì .catch
. Điều đó thường (nhưng không phải luôn luôn) thuận tiện hơn.
Nhưng ở cấp cao nhất của code, khi chúng ta ở ngoài bất kỳ hàmasync
nào, chúng ta không thể sử dụng về mặt cú pháp await
, do đó, đây là một cách thông thường để thêm .then/catch
vào để xử lý kết quả cuối cùng hoặc lỗi thông qua, như trong (*)
ví dụ ở trên.async/await
hoạt động tốt với Promise.all
Khi chúng ta cần chờ đợi nhiều lời hứa, chúng ta có thể gói chúng lại Promise.all
và sau đó await
:
// wait for the array of results
let results = await Promise.all([
fetch(url1),
fetch(url2),
...
]);
Trong trường hợp có lỗi, nó sẽ lan truyền như bình thường, từ lời hứa thất bại đến Promise.all
, và sau đó trở thành một ngoại lệ mà chúng ta có thể bắt gặp khi sử dụng try..catch
xung quanh cuộc gọi.
4. Tóm lược
Các từ khóaasync
trước khi một hàm có hai tác dụng:
- Làm cho nó luôn luôn trả lại một lời hứa.
- Cho phép
await
được sử dụng trong nó.
Các từ khóaawait
trước một lời hứa làm cho chờ đợi cho đến khi hoạt Javascript rằng Settles lời hứa, và sau đó:
- Nếu đó là một lỗi, ngoại lệ được tạo ra – giống như khi
throw error
được gọi tại chính vị trí đó. - Nếu không, nó trả về kết quả.
Họ cùng nhau cung cấp một khung tuyệt vời để viết code không đồng bộ, dễ đọc và viết.
Với async/await
chúng ta hiếm khi cần phải viết promise.then/catch
, nhưng chúng ta vẫn không nên quên rằng chúng dựa trên những lời hứa, bởi vì đôi khi (ví dụ trong phạm vi ngoài cùng), chúng ta phải sử dụng các phương pháp này. Cũng Promise.all
tốt khi chúng ta đang chờ đợi nhiều nhiệm vụ cùng một lúc.
Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.
Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:
Chào thân ái và quyết thắng!