W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
async/await 是以更舒適的方式使用 promise 的一種特殊語法,同時它也非常易于理解和使用。
讓我們以 ?async
? 這個關(guān)鍵字開始。它可以被放置在一個函數(shù)前面,如下所示:
async function f() {
return 1;
}
在函數(shù)前面的 “async” 這個單詞表達了一個簡單的事情:即這個函數(shù)總是返回一個 promise。其他值將自動被包裝在一個 resolved 的 promise 中。
例如,下面這個函數(shù)返回一個結(jié)果為 1
的 resolved promise,讓我們測試一下:
async function f() {
return 1;
}
f().then(alert); // 1
……我們也可以顯式地返回一個 promise,結(jié)果是一樣的:
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
所以說,async
確保了函數(shù)返回一個 promise,也會將非 promise 的值包裝進去。很簡單,對吧?但不僅僅這些。還有另外一個叫 await
的關(guān)鍵詞,它只在 async
函數(shù)內(nèi)工作,也非??帷?
語法如下:
// 只在 async 函數(shù)內(nèi)工作
let value = await promise;
關(guān)鍵字 await
讓 JavaScript 引擎等待直到 promise 完成(settle)并返回結(jié)果。
這里的例子就是一個 1 秒后 resolve 的 promise:
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // 等待,直到 promise resolve (*)
alert(result); // "done!"
}
f();
這個函數(shù)在執(zhí)行的時候,“暫?!痹诹?nbsp;(*)
那一行,并在 promise settle 時,拿到 result
作為結(jié)果繼續(xù)往下執(zhí)行。所以上面這段代碼在一秒后顯示 “done!”。
讓我們強調(diào)一下:await
實際上會暫停函數(shù)的執(zhí)行,直到 promise 狀態(tài)變?yōu)?settled,然后以 promise 的結(jié)果繼續(xù)執(zhí)行。這個行為不會耗費任何 CPU 資源,因為 JavaScript 引擎可以同時處理其他任務(wù):執(zhí)行其他腳本,處理事件等。
相比于 promise.then
,它只是獲取 promise 的結(jié)果的一個更優(yōu)雅的語法。并且也更易于讀寫。
不能在普通函數(shù)中使用 ?
await
?如果我們嘗試在非 async 函數(shù)中使用
await
,則會報語法錯誤:
function f() { let promise = Promise.resolve(1); let result = await promise; // Syntax error }
如果我們忘記在函數(shù)前面寫
async
關(guān)鍵字,我們可能會得到一個這個錯誤。就像前面說的,await
只在async
函數(shù)中有效。
讓我們拿 Promise 鏈 那一章的 showAvatar()
例子,并將其改寫成 async/await
的形式:
await
? 替換掉 ?.then
? 的調(diào)用。async
? 關(guān)鍵字,以使它們能工作。async function showAvatar() {
// 讀取我們的 JSON
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// 讀取 github 用戶信息
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// 顯示頭像
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// 等待 3 秒
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
簡潔明了,是吧?比之前可強多了。
現(xiàn)代瀏覽器在 modules 里允許頂層的 ?
await
?在現(xiàn)代瀏覽器中,當(dāng)我們處于一個 module 中時,那么在頂層使用
await
也是被允許的。我們將在 模塊 (Module) 簡介 中詳細(xì)學(xué)習(xí) modules。
例如:
// 我們假設(shè)此代碼在 module 中的頂層運行 let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); console.log(user);
如果我們沒有使用 modules,或者必須兼容 舊版本瀏覽器 ,那么這兒還有一個通用的方法:包裝到匿名的異步函數(shù)中。
像這樣:
(async () => { let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); ... })();
?
await
? 接受 “thenables”像
promise.then
那樣,await
允許我們使用 thenable 對象(那些具有可調(diào)用的then
方法的對象)。這里的想法是,第三方對象可能不是一個 promise,但卻是 promise 兼容的:如果這些對象支持.then
,那么就可以對它們使用await
。
這有一個用于演示的
Thenable
類,下面的await
接受了該類的實例:
class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // 1000ms 后使用 this.num*2 進行 resolve setTimeout(() => resolve(this.num * 2), 1000); // (*) } } async function f() { // 等待 1 秒,之后 result 變?yōu)?2 let result = await new Thenable(1); alert(result); } f();
如果
await
接收了一個非 promise 的但是提供了.then
方法的對象,它就會調(diào)用這個.then
方法,并將內(nèi)建的函數(shù)resolve
和reject
作為參數(shù)傳入(就像它對待一個常規(guī)的Promise
executor 時一樣)。然后await
等待直到這兩個函數(shù)中的某個被調(diào)用(在上面這個例子中發(fā)生在(*)
行),然后使用得到的結(jié)果繼續(xù)執(zhí)行后續(xù)任務(wù)。
Class 中的 async 方法
要聲明一個 class 中的 async 方法,只需在對應(yīng)方法前面加上
async
即可:
class Waiter { async wait() { return await Promise.resolve(1); } } new Waiter() .wait() .then(alert); // 1(alert 等同于 result => alert(result))
這里的含義是一樣的:它確保了方法的返回值是一個 promise 并且可以在方法中使用
await
。
如果一個 promise 正常 resolve,await promise
返回的就是其結(jié)果。但是如果 promise 被 reject,它將 throw 這個 error,就像在這一行有一個 throw
語句那樣。
這個代碼:
async function f() {
await Promise.reject(new Error("Whoops!"));
}
……和下面是一樣的:
async function f() {
throw new Error("Whoops!");
}
在真實開發(fā)中,promise 可能需要一點時間后才 reject。在這種情況下,在 await
拋出(throw)一個 error 之前會有一個延時。
我們可以用 try..catch
來捕獲上面提到的那個 error,與常規(guī)的 throw
使用的是一樣的方式:
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
alert(err); // TypeError: failed to fetch
}
}
f();
如果有 error 發(fā)生,執(zhí)行控制權(quán)馬上就會被移交至 catch
塊。我們也可以用 try
包裝多行 await
代碼:
async function f() {
try {
let response = await fetch('/no-user-here');
let user = await response.json();
} catch(err) {
// 捕獲到 fetch 和 response.json 中的錯誤
alert(err);
}
}
f();
如果我們沒有 try..catch
,那么由異步函數(shù) f()
的調(diào)用生成的 promise 將變?yōu)?rejected。我們可以在函數(shù)調(diào)用后面添加 .catch
來處理這個 error:
async function f() {
let response = await fetch('http://no-such-url');
}
// f() 變成了一個 rejected 的 promise
f().catch(alert); // TypeError: failed to fetch // (*)
如果我們忘了在這添加 .catch
,那么我們就會得到一個未處理的 promise error(可以在控制臺中查看)。我們可以使用在 使用 promise 進行錯誤處理 一章中所講的全局事件處理程序 unhandledrejection
來捕獲這類 error。
?
async/await
? 和 ?promise.then/catch
?當(dāng)我們使用
async/await
時,幾乎就不會用到.then
了,因為await
為我們處理了等待。并且我們使用常規(guī)的try..catch
而不是.catch
。這通常(但不總是)更加方便。
但是當(dāng)我們在代碼的頂層時,也就是在所有
async
函數(shù)之外,我們在語法上就不能使用await
了,所以這時候通常的做法是添加.then/catch
來處理最終的結(jié)果(result)或掉出來的(falling-through)error,例如像上面那個例子中的(*)
行那樣。
?
async/await
? 可以和 ?Promise.all
? 一起使用當(dāng)我們需要同時等待多個 promise 時,我們可以用
Promise.all
把它們包裝起來,然后使用await
:
// 等待結(jié)果數(shù)組 let results = await Promise.all([ fetch(url1), fetch(url2), ... ]);
如果出現(xiàn) error,也會正常傳遞,從失敗了的 promise 傳到
Promise.all
,然后變成我們能通過使用try..catch
在調(diào)用周圍捕獲到的異常(exception)。
函數(shù)前面的關(guān)鍵字 ?async
? 有兩個作用:
await
?。Promise 前的關(guān)鍵字 await
使 JavaScript 引擎等待該 promise settle,然后:
throw error
? 一樣。這兩個關(guān)鍵字一起提供了一個很好的用來編寫異步代碼的框架,這種代碼易于閱讀也易于編寫。
有了 async/await
之后,我們就幾乎不需要使用 promise.then/catch
,但是不要忘了它們是基于 promise 的,因為有些時候(例如在最外層作用域)我們不得不使用這些方法。并且,當(dāng)我們需要同時等待需要任務(wù)時,Promise.all
是很好用的。
重寫下面這個來自 Promise 鏈 一章的示例代碼,使用 async/await
而不是 .then/catch
:
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new Error(response.status);
}
});
}
loadJson('https://javascript.info/no-such-user.json')
.catch(alert); // Error: 404
解析在代碼下面:
async function loadJson(url) { // (1)
let response = await fetch(url); // (2)
if (response.status == 200) {
let json = await response.json(); // (3)
return json;
}
throw new Error(response.status);
}
loadJson('https://javascript.info/no-such-user.json')
.catch(alert); // Error: 404 (4)
解析:
loadJson
? 變?yōu)?nbsp;?async
?。.then
? 都替換為 ?await
?。return response.json()
? 而不用等待它,像這樣:if (response.status == 200) {
return response.json(); // (3)
}
然后外部的代碼就必須 await
這個 promise resolve。在本例中它無關(guān)緊要。
loadJson
? 拋出的 error 被 ?.catch
? 處理了。在這兒我們我們不能使用 ?await loadJson(…)
?,因為我們不是在一個 ?async
? 函數(shù)中。下面你可以看到 “rethrow” 的例子。讓我們來用 async/await
重寫它,而不是使用 .then/catch
。
同時,我們可以在 demoGithubUser
中使用循環(huán)以擺脫遞歸:在 async/await
的幫助下很容易實現(xiàn)。
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
});
}
// 詢問用戶名,直到 github 返回一個合法的用戶
function demoGithubUser() {
let name = prompt("Enter a name?", "iliakan");
return loadJson(`https://api.github.com/users/${name}`)
.then(user => {
alert(`Full name: ${user.name}.`);
return user;
})
.catch(err => {
if (err instanceof HttpError && err.response.status == 404) {
alert("No such user, please reenter.");
return demoGithubUser();
} else {
throw err;
}
});
}
demoGithubUser();
這里沒有什么技巧。只需要將 demoGithubUser
中的 .catch
替換為 try...catch
,然后在需要的地方加上 async/await
即可:
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
async function loadJson(url) {
let response = await fetch(url);
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
}
// 詢問用戶名,直到 github 返回一個合法的用戶
async function demoGithubUser() {
let user;
while(true) {
let name = prompt("Enter a name?", "iliakan");
try {
user = await loadJson(`https://api.github.com/users/${name}`);
break; // 沒有 error,退出循環(huán)
} catch(err) {
if (err instanceof HttpError && err.response.status == 404) {
// 循環(huán)將在 alert 后繼續(xù)
alert("No such user, please reenter.");
} else {
// 未知的 error,再次拋出(rethrow)
throw err;
}
}
}
alert(`Full name: ${user.name}.`);
return user;
}
demoGithubUser();
我們有一個名為 f
的“普通”函數(shù)。你會怎樣調(diào)用 async
函數(shù) wait()
并在 f
中使用其結(jié)果?
async function wait() {
await new Promise(resolve => setTimeout(resolve, 1000));
return 10;
}
function f() {
// ……這里你應(yīng)該怎么寫?
// 我們需要調(diào)用 async wait() 并等待以拿到結(jié)果 10
// 記住,我們不能使用 "await"
}
P.S. 這個任務(wù)其實很簡單,但是對于 async/await 新手開發(fā)者來說,這個問題卻很常見。
在這種情況下,知道其內(nèi)部工作原理會很有幫助。
只需要把 async
調(diào)用當(dāng)作 promise 對待,并在它的后面加上 .then
即可:
async function wait() {
await new Promise(resolve => setTimeout(resolve, 1000));
return 10;
}
function f() {
// 1 秒后顯示 10
wait().then(result => alert(result));
}
f();
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: