async/await的典型應(yīng)用場景
function delay(num){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(num*2);
},2000)
})
}
async function plus(){
let a=await delay(2);
let b=await delay(3);
let c=await delay(5);
console.log(a+b+c);
}
plus();//20
一、async/await的優(yōu)點(diǎn)
1)方便級聯(lián)調(diào)用
即調(diào)用依次發(fā)生的場景趋厉;
2)同步代碼編寫方式
Promise使用then函數(shù)進(jìn)行鏈?zhǔn)秸{(diào)用寨闹,一直點(diǎn)點(diǎn)點(diǎn),是一種從左向右的橫向?qū)懛ň耍籥sync/await從上到下繁堡,順序執(zhí)行,就像寫同步代碼一樣乡数,更符合代碼編寫習(xí)慣椭蹄;
3)多個參數(shù)傳遞
Promise的then函數(shù)只能傳遞一個參數(shù),雖然可以通過包裝成對象來傳遞多個參數(shù)净赴,但是會導(dǎo)致傳遞冗余信息绳矩,頻繁的解析又重新組合參數(shù),比較麻煩劫侧;async/await沒有這個限制埋酬,可以當(dāng)做普通的局部變量來處理哨啃,用let或者const定義的塊級變量想怎么用就怎么用,想定義幾個就定義幾個写妥,完全沒有限制拳球,也沒有冗余工作;
4)同步代碼和異步代碼可以一起編寫
使用Promise的時候最好將同步代碼和異步代碼放在不同的then節(jié)點(diǎn)中珍特,這樣結(jié)構(gòu)更加清晰祝峻;async/await整個書寫習(xí)慣都是同步的,不需要糾結(jié)同步和異步的區(qū)別扎筒,當(dāng)然莱找,異步過程需要包裝成一個Promise對象放在await關(guān)鍵字后面;
5)基于協(xié)程
Promise是根據(jù)函數(shù)式編程的范式嗜桌,對異步過程進(jìn)行了一層封裝奥溺,async/await基于協(xié)程的機(jī)制,是真正的“保存上下文骨宠,控制權(quán)切換……控制權(quán)恢復(fù)浮定,取回上下文”這種機(jī)制,是對異步過程更精確的一種描述层亿;
6)async/await是對Promise的優(yōu)化
async/await是基于Promise的桦卒,是進(jìn)一步的一種優(yōu)化,不過在寫代碼時匿又,Promise本身的API出現(xiàn)得很少方灾,很接近同步代碼的寫法;
二碌更、協(xié)程
進(jìn)程>線程>協(xié)程
協(xié)程的意思是多個線程相互協(xié)作完成異步任務(wù)裕偿,大致流程如下:
1)協(xié)程A開始執(zhí)行;
2)協(xié)程A執(zhí)行到一半针贬,進(jìn)入暫停击费,執(zhí)行權(quán)轉(zhuǎn)移到協(xié)程B拢蛋;
3)一段時間后桦他,協(xié)程B交還執(zhí)行權(quán);
4)協(xié)程A恢復(fù)執(zhí)行谆棱;
協(xié)程是一個無優(yōu)先級的子程序調(diào)度組件快压,允許子程序在特定的地點(diǎn)掛起恢復(fù);
線程包含于進(jìn)程垃瞧,協(xié)程包含于線程蔫劣,只要內(nèi)存足夠,一個線程中可以有任意多個協(xié)程个从,但某一個時刻只能有一個協(xié)程在運(yùn)行脉幢,多個協(xié)程分享該線程分配到的計(jì)算機(jī)資源歪沃;
就實(shí)際使用理解來說,協(xié)程允許我們寫同步代碼的邏輯嫌松,卻做著異步的事沪曙,避免了回調(diào)嵌套,使得代碼邏輯清晰萎羔;
何時掛起液走,喚醒協(xié)程:協(xié)程是為了使用異步的優(yōu)勢,異步操作是為了避免IO操作阻塞線程贾陷,那么協(xié)程掛起的時刻應(yīng)該是當(dāng)前協(xié)程發(fā)起異步操作的時候缘眶,而喚醒應(yīng)該在其他協(xié)程退出,并且他的異步操作完成時髓废;
單線程內(nèi)開啟協(xié)程巷懈,一旦遇到io,從應(yīng)用程序級別(而非操作系統(tǒng))控制切換對比操作系統(tǒng)控制線程的切換慌洪,用戶在單線程內(nèi)控制協(xié)程的切換砸喻,優(yōu)點(diǎn)如下:
1)協(xié)程的切換開銷更小,屬于程序級別的切換蒋譬,操作系統(tǒng)完全感知不到割岛,因而更加輕量級;
2)單線程內(nèi)就可以實(shí)現(xiàn)并發(fā)的效果犯助,最大限度地利用cpu癣漆;
協(xié)程的第一大優(yōu)勢是具有極高的執(zhí)行效率,因?yàn)樽映绦蚯袚Q不是線程切換剂买,而是由程序自身控制惠爽,因此沒有線程切換的開銷,和多線程比瞬哼,線程數(shù)量越多婚肆,協(xié)程的性能優(yōu)勢就越明顯;
協(xié)程的第二大優(yōu)勢是不需要多線程的鎖機(jī)制坐慰,因?yàn)橹挥幸粋€線程较性,也不存在同時寫變量沖突,在協(xié)程中控制共享資源不加鎖结胀,只需要判斷狀態(tài)就好了赞咙,所以執(zhí)行效率比多線程高很多;
協(xié)程看上去也是子程序糟港,但執(zhí)行過程中攀操,在子程序內(nèi)部可中斷,然后轉(zhuǎn)而執(zhí)行別的子程序秸抚,在適當(dāng)?shù)臅r候再返回來接著執(zhí)行速和,需要注意的是:在一個子程序中中斷歹垫,去執(zhí)行其他子程序,這并不是函數(shù)調(diào)用颠放,有點(diǎn)類似于CPU的中斷县钥;
用汽車和公路舉個例子:js公路只是單行道(主線程),但是有很多車道(輔助線程)都可以匯入車流(異步任務(wù)完成后回調(diào)進(jìn)入主線程的任務(wù)隊(duì)列)慈迈;generator把js公路變成了多車道(協(xié)程實(shí)現(xiàn))若贮,但是同一時間只有一個車道上的車能開(依然單線程),不過可以自由變道(移交控制權(quán))痒留;
三谴麦、async關(guān)鍵字
1)表明程序里面可能有異步過程
async關(guān)鍵字表明程序里面可能有異步過程,里面可以有await關(guān)鍵字伸头;當(dāng)然全部是同步代碼也沒關(guān)系匾效,但是這樣async關(guān)鍵字就顯得多余了;
2)非阻塞
async函數(shù)里面如果有異步過程會等待恤磷,但是async函數(shù)本身會馬上返回面哼,不會阻塞當(dāng)前線程,可以簡單認(rèn)為扫步,async函數(shù)工作在主線程魔策,同步執(zhí)行,不會阻塞界面渲染河胎,async函數(shù)內(nèi)部由await關(guān)鍵字修飾的異步過程闯袒,工作在相應(yīng)的協(xié)程上,會阻塞等待異步任務(wù)的完成再返回游岳;
3)async函數(shù)返回類型為Promise對象
這是和普通函數(shù)本質(zhì)上不同的地方政敢,也是使用時重點(diǎn)注意的地方;
(1)return newPromise();
這個符合async函數(shù)本意胚迫;
(2)return data;
這個是同步函數(shù)的寫法喷户,這里是要特別注意的,這個時候访锻,其實(shí)就相當(dāng)于Promise.resolve(data);
還是一個Promise對象褪尝,但是在調(diào)用async函數(shù)的地方通過簡單的=是拿不到這個data的,因?yàn)榉祷刂凳且粋€Promise對象朗若,所以需要用.then(data => { })
函數(shù)才可以拿到這個data恼五;
(3)如果沒有返回值昌罩,相當(dāng)于返回了Promise.resolve(undefined);
4)無等待
聯(lián)想到Promise的特點(diǎn)哭懈,在沒有await的情況下執(zhí)行async函數(shù),它會立即執(zhí)行茎用,返回一個Promise對象遣总,并且絕對不會阻塞后面的語句睬罗,這和普通返回Promise對象的函數(shù)并無二致;
5)await不處理異步error
await是不管異步過程的reject(error)
消息的旭斥,async函數(shù)返回的這個Promise對象的catch函數(shù)負(fù)責(zé)統(tǒng)一抓取內(nèi)部所有異步過程的錯誤容达;async函數(shù)內(nèi)部只要有一個異步過程發(fā)生錯誤,整個執(zhí)行過程就中斷垂券,這個返回的Promise對象的catch就能抓取到這個錯誤花盐;
5)async函數(shù)的執(zhí)行
async函數(shù)執(zhí)行和普通函數(shù)一樣,函數(shù)名帶個()就可以了菇爪,參數(shù)個數(shù)隨意算芯,沒有限制,也需要有async關(guān)鍵字凳宙;只是返回值是一個Promise對象熙揍,可以用then函數(shù)得到返回值,用catch抓整個流程中發(fā)生的錯誤氏涩;
async function timeout(){
return "hello world";
}
console.log(timeout());
console.log("誰先執(zhí)行呢届囚?");
/* Promise { 'hello world' }
誰先執(zhí)行呢? */
//async函數(shù)返回的是promise對象是尖,所以如果想調(diào)用輸出結(jié)果可以直接用then語句
timeout().then((res)=>{
console.log(res);//hello world
})
四意系、await關(guān)鍵字
1)await只能在async函數(shù)內(nèi)部使用
不能放在普通函數(shù)里面,否則會報(bào)錯饺汹;
2)await關(guān)鍵字后面跟Promise對象
在Pending狀態(tài)時昔字,相應(yīng)的協(xié)程會交出控制權(quán),進(jìn)入等待狀態(tài)首繁,這是協(xié)程的本質(zhì)作郭;
3)await是async wait的意思
wait的是resolve(data)
的消息,并把數(shù)據(jù)data返回弦疮,比如下面代碼中夹攒,當(dāng)Promise對象由Pending變?yōu)镽esolved的時候,變量a就等于data胁塞,然后再順序執(zhí)行下面的語句console.log(a)
咏尝,這真的是等待,真的是順序執(zhí)行啸罢,表現(xiàn)和同步代碼幾乎一模一樣编检;
async function jianshu(data){
const a=await new Promise((resolve,reject)=>{
return resolve(data);
});
console.log(a);
}
jianshu("jianshu");//jianshu
4)await后面也可以跟同步代碼
不過系統(tǒng)會自動將其轉(zhuǎn)化成一個Promsie對象
const a = await 'hello world'
// 相當(dāng)于
const a = await Promise.resolve('hello world');
// 跟同步代碼是一樣的狭姨,還不如省事點(diǎn)撑碴,直接去掉await關(guān)鍵字
const a = 'hello world';
5)await對于失敗消息的處理
await只關(guān)心異步過程成功的消息`resolve(data)`,拿到相應(yīng)的數(shù)據(jù)data叠国,至于失敗消息`reject(error)`衩匣,不關(guān)心不處理蕾总;對于錯誤的處理有以下幾種方法供選擇:
**讓await后面的Promise對象自己catch粥航;**
async function jianshu(data){
const a=await new Promise((resolve,reject)=>{
//resolve(data);
reject("error");
}).then((res)=>{
return res;
}).catch((err)=>{
return err;
})
console.log(a);
}
jianshu("jianshu");//error
**也可以讓外面的async函數(shù)返回的Promise對象統(tǒng)一catch;**
function delay(num){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
//resolve(num*2);
reject("error");
},200)
})
}
async function test() {
let a = await delay(3).then((res) => {
return res;
}).catch((err) => {
return err;
});
console.log(a);
}
test();//error
**像同步代碼一樣生百,放在一個try...catch結(jié)構(gòu)中递雀;**
async function didMount() {
// 將異步和同步的代碼放在一個try..catch中,異常都能抓到
try {
let array = null;
let data = await asyncFunction();
if (array.length > 0) { // 這里會拋出異常蚀浆,下面的catch也能抓到
array.push(data);
}
} catch (error) {
console.log(error);
}
}
async function asyncFunction(){
return 1;
}
didMount();//TypeError: Cannot read property 'length' of null
6)await對于結(jié)果的處理
await是個運(yùn)算符缀程,用于組成表達(dá)式,await表達(dá)式的運(yùn)算結(jié)果取決于它等的東西.
如果它等到的不是一個Promise對象市俊,那么await表達(dá)式的運(yùn)算結(jié)果就是它等到的東西杠输;
function delay(num){
setTimeout(()=>{
return num*2;
},2000)
}
async function plus(){
let a=await delay(3);
/*這里await所等待的只是一個數(shù)值,因此不會阻塞后面的程序執(zhí)行秕衙,由于delay需要延時兩秒后才能執(zhí)行蠢甲,因此程序隔過await先執(zhí)行下面的輸出語句,又由于執(zhí)行輸出時a的值還沒有定義据忘,所以會輸出undefined*/
console.log(a);
}
plus();//undefined
如果它等到的是一個Promise對象鹦牛,await就忙起來了,它會阻塞其后面的代碼勇吊,等著Promise對象resolve曼追,然后得到resolve的值,作為await表達(dá)式的運(yùn)算結(jié)果汉规;雖然是阻塞礼殊,但async函數(shù)調(diào)用并不會造成阻塞,它內(nèi)部所有的阻塞都被封裝在一個Promise對象中異步執(zhí)行针史,這也正是await必須用在async函數(shù)中的原因晶伦;
function delay(num){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(num*2);
},2000)
})
}
async function plus(){
let a=await delay(2);
let b=await delay(3);
let c=await delay(5);
//這里await會阻塞后面程序的執(zhí)行,只有當(dāng)前面的三個await執(zhí)行完了之后才執(zhí)行后面的語句
console.log(a+b+c);
}
plus();//20