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