相對(duì)于回調(diào)函數(shù)來說颠印,Promise
是一種相對(duì)優(yōu)雅的選擇昧诱。那么有沒有更好的方案呢?答案就是async/await
潭辈。
優(yōu)勢(shì)主要體現(xiàn)在鸯屿,級(jí)聯(lián)調(diào)用,也就是幾個(gè)調(diào)用依次發(fā)生的場(chǎng)景把敢。
async/await
寄摆。被稱為到目前最優(yōu)雅的異步過程解決方案,不知道你是否認(rèn)同修赞,反正我是信了婶恼。
相對(duì)于Promise
桑阶,async/await
有什么優(yōu)點(diǎn)?
比較場(chǎng)景: 級(jí)聯(lián)調(diào)用勾邦,也就是幾個(gè)調(diào)用依次發(fā)生的場(chǎng)景
-
Promise
主要用then
函數(shù)的鏈?zhǔn)秸{(diào)用蚣录,一直點(diǎn)點(diǎn)點(diǎn),是一種從左向右的橫向?qū)懛ā?br>async/await
從上到下眷篇,順序執(zhí)行萎河,就像寫同步代碼一樣。這更符合人編寫代碼的習(xí)慣 -
Promise
的then
函數(shù)只能傳遞一個(gè)參數(shù)蕉饼,雖然可以通過包裝成對(duì)象虐杯,但是這會(huì)導(dǎo)致傳遞冗余信息,頻繁的解析又重新組合參數(shù)昧港,比較麻煩擎椰。
async/await
沒有這個(gè)限制,就當(dāng)做普通的局部變量來處理好了创肥,用let
或者const
定義的塊級(jí)變量达舒,想怎么用就怎么用,想定義幾個(gè)就定義幾個(gè)叹侄,完全沒有限制休弃,也沒有冗余的工作。 -
Promise
在使用的時(shí)候最好將同步代碼和異步代碼放在不同的then
節(jié)點(diǎn)中圈膏,這樣結(jié)構(gòu)更加清晰。
async/await
整個(gè)書寫習(xí)慣都是同步的篙骡,不需要糾結(jié)同步和異步的區(qū)別稽坤。當(dāng)然,異步過程需要包裝成一個(gè)Promise
對(duì)象糯俗,放在await
關(guān)鍵字后面尿褪,這點(diǎn)還是要牢記的。 -
Promise
是根據(jù)函數(shù)式編程的范式得湘,對(duì)異步過程進(jìn)行了一層封裝杖玲。
async/await
是基于協(xié)程的機(jī)制,是真正的“保存上下文淘正,控制權(quán)切換 … … 控制權(quán)恢復(fù)摆马,取回上下文”這種機(jī)制,是對(duì)異步過程更精確的一種描述鸿吆。
進(jìn)程囤采、線程和協(xié)程的理解
上面的文章很好地解釋了這幾個(gè)概念的區(qū)別。
如果不糾結(jié)細(xì)節(jié)惩淳,可以簡(jiǎn)單地認(rèn)為:進(jìn)程 > 線程 > 協(xié)程蕉毯;
協(xié)程可以獨(dú)立完成一些與界面無關(guān)的工作,不會(huì)阻塞主線程渲染界面,也就是不會(huì)卡代虾。
協(xié)程进肯,雖然小一點(diǎn),不過能完成我們程序員交給的任務(wù)棉磨。而且我們可以自由控制運(yùn)行和阻塞狀態(tài)江掩,不需要求助于高大上的系統(tǒng)調(diào)度,這才是重點(diǎn)含蓉。
-
async/await
是基于Promise
的频敛,是進(jìn)一步的一種優(yōu)化。不過再寫代碼的時(shí)候馅扣,Promise
本身的API
出現(xiàn)得很少斟赚,很接近同步代碼的寫法。
await
關(guān)鍵字使用時(shí)有哪些注意點(diǎn)差油?
- 只能放在
async
函數(shù)內(nèi)部使用拗军,不能放在普通函數(shù)里面,否則會(huì)報(bào)錯(cuò)蓄喇。 - 后面放
Promise
對(duì)象发侵,在Pending
狀態(tài)時(shí),相應(yīng)的協(xié)程會(huì)交出控制權(quán)妆偏,進(jìn)入等待狀態(tài)刃鳄。這個(gè)是本質(zhì)。 -
await
是async wait
的意思钱骂,wait
的是resolve(data)
消息叔锐,并把數(shù)據(jù)data
返回。比如见秽,下面代碼中愉烙,當(dāng)Promise
對(duì)象由Pending
變?yōu)?code>Resolved的時(shí)候,變量a
就等于data
解取;然后再順序執(zhí)行下面的語(yǔ)句console.log(a);
這真的是等待步责,真的是順序執(zhí)行,表現(xiàn)和同步代碼幾乎一模一樣禀苦。
const a = await new Promise((resolve, reject) => {
// async process ...
return resolve(data);
});
console.log(a);
-
await
后面也可以跟同步代碼蔓肯,不過系統(tǒng)會(huì)自動(dòng)轉(zhuǎn)化成一個(gè)Promise
對(duì)象。
比如
const a = await 'hello world';
其實(shí)就相當(dāng)于
const a = await Promise.resolve('hello world');
這跟同步代碼
const a = 'hello world';
是一樣的伦忠,還不如省點(diǎn)事省核,去掉這里的await關(guān)鍵字。
-
await
只關(guān)心異步過程成功的消息resolve(data)
昆码,拿到相應(yīng)的數(shù)據(jù)data
气忠。至于失敗消息reject(error)
邻储,不關(guān)心,不處理旧噪。
當(dāng)然對(duì)于錯(cuò)誤消息的處理吨娜,有以下幾種方法供選擇:
(1)讓await
后面的Promise
對(duì)象自己catch
(2)也可以讓外面的async
函數(shù)返回的Promise
對(duì)象統(tǒng)一catch
(3)像同步代碼一樣,放在一個(gè)try...catch
結(jié)構(gòu)中
async
關(guān)鍵字使用時(shí)有哪些注意點(diǎn)淘钟?
- 有了這個(gè)
async
關(guān)鍵字宦赠,只是表明里面可能有異步過程,里面可以有await
關(guān)鍵字米母。當(dāng)然勾扭,全部是同步代碼也沒關(guān)系。當(dāng)然铁瞒,這時(shí)候這個(gè)async
關(guān)鍵字就顯得多余了妙色。不是不能加,而是不應(yīng)該加慧耍。 -
async
函數(shù)身辨,如果里面有異步過程,會(huì)等待芍碧;
但是async
函數(shù)本身會(huì)馬上返回煌珊,不會(huì)阻塞當(dāng)前線程。
可以簡(jiǎn)單認(rèn)為泌豆,
async
函數(shù)工作在主線程定庵,同步執(zhí)行,不會(huì)阻塞界面渲染踪危。
async
函數(shù)內(nèi)部由async
關(guān)鍵字修飾的異步過程洗贰,工作在相應(yīng)的協(xié)程上,會(huì)阻塞等待異步任務(wù)的完成再返回陨倡。
async
函數(shù)的返回值是一個(gè)Promise
對(duì)象,這個(gè)是和普通函數(shù)本質(zhì)不同的地方许布。這也是使用時(shí)重點(diǎn)注意的地方
(1)return newPromise();
這個(gè)符合async
函數(shù)本意兴革;
(2)return data;
這個(gè)是同步函數(shù)的寫法,這里是要特別注意的蜜唾。這個(gè)時(shí)候杂曲,其實(shí)就相當(dāng)于Promise.resolve(data);
還是一個(gè)Promise
對(duì)象。
在調(diào)用async
函數(shù)的地方通過簡(jiǎn)單的=
是拿不到這個(gè)data
的袁余。
那么怎么樣拿到這個(gè)data
呢擎勘?
很簡(jiǎn)單,返回值是一個(gè)Promise
對(duì)象颖榜,用.then(data => { })
函數(shù)就可以棚饵。
(3)如果沒有返回煤裙,相當(dāng)于返回了Promise.resolve(undefined);
-
await
是不管異步過程的reject(error)
消息的,async
函數(shù)返回的這個(gè)Promise
對(duì)象的catch
函數(shù)就負(fù)責(zé)統(tǒng)一抓取內(nèi)部所有異步過程的錯(cuò)誤噪漾。
async
函數(shù)內(nèi)部只要有一個(gè)異步過程發(fā)生錯(cuò)誤硼砰,整個(gè)執(zhí)行過程就中斷,這個(gè)返回的Promise
對(duì)象的catch
就能抓到這個(gè)錯(cuò)誤欣硼。 -
async
函數(shù)執(zhí)行和普通函數(shù)一樣题翰,函數(shù)名帶個(gè)()
就可以了,參數(shù)個(gè)數(shù)隨意诈胜,沒有限制豹障;也需要有async
關(guān)鍵字。
只是返回值是一個(gè)Promise
對(duì)象焦匈,可以用then
函數(shù)得到返回值血公,用catch
抓去整個(gè)流程中發(fā)生的錯(cuò)誤。
基本套路
Step1:用Promise
對(duì)象包裝異步過程括授,這個(gè)和Promise
的使用一樣坞笙。只是參數(shù)個(gè)數(shù)隨意,沒有限制荚虚。
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('sleep for ' + ms + ' ms');
}, ms);
});
}
Step2:定義異步流程薛夜,可以將按照需要定制,就像寫同步代碼那樣
async function asyncFunction() {
console.time('asyncFunction total executing:');
const sleep1 = await sleep(2000);
console.log('sleep1: ' + sleep1);
const [sleep2, sleep3, sleep4]= await Promise.all([sleep(2000), sleep(1000), sleep(1500)]);
console.log('sleep2: ' + sleep2);
console.log('sleep3: ' + sleep3);
console.log('sleep4: ' + sleep4);
const sleepRace = await Promise.race([sleep(3000), sleep(1000), sleep(1000)]);
console.log('sleep race: ' + sleepRace);
console.timeEnd('asyncFunction total executing:');
return 'asyncFunction done.' // 這個(gè)可以不返回版述,這里只是做個(gè)標(biāo)記梯澜,為了顯示流程
}
Step3:像普通函數(shù)調(diào)用async
函數(shù),在then函數(shù)中獲取整個(gè)流程的返回信息渴析,在catch
函數(shù)統(tǒng)一處理出錯(cuò)信息
asyncFunction().then(data => {
console.log(data); // asyncFunction return 的內(nèi)容在這里獲取
}).catch(error => {
console.log(error); // asyncFunction 的錯(cuò)誤統(tǒng)一在這里抓取
});
console.log('after asyncFunction code executing....'); // 這個(gè)代表asyncFunction函數(shù)后的代碼晚伙,
// 顯示asyncFunction本身會(huì)立即返回,不會(huì)阻塞主線程
流程解析
上面的代碼執(zhí)行之后俭茧,輸出的log
如下咆疗,顯示了代碼執(zhí)行流程
after asyncFunction code executing....
sleep1: sleep for 2000 ms
sleep2: sleep for 2000 ms
sleep3: sleep for 1000 ms
sleep4: sleep for 1500 ms
sleep race: sleep for 1000 ms
asyncFunction total executing:: 5006.276123046875ms
asyncFunction done.
-
after asyncFunction code executing....
代碼位置在async
函數(shù)asyncFunction()
調(diào)用之后,反而先輸出母债。這說明async
函數(shù)asyncFunction()
調(diào)用之后會(huì)馬上返回午磁,不會(huì)阻塞主線程。 -
sleep1: sleep for 2000 ms
這是第一個(gè)await
之后的第一個(gè)異步過程毡们,最先執(zhí)行迅皇,也最先完成,說明后面的代碼衙熔,不論是同步和異步登颓,都在等他執(zhí)行完畢。 -
sleep2 ~ sleep4
這是第二個(gè)await
之后的Promise.all()
異步過程红氯。這是“比慢模式”框咙,三個(gè)sleep
都完成后咕痛,再運(yùn)行下面的代碼,耗時(shí)最長(zhǎng)的是2000ms
扁耐; -
sleep race: sleep for 1000 ms
這是第三個(gè)await
之后的Promise.race()
異步過程暇检。這是“比快模式”,耗時(shí)最短sleep
都完成后婉称,就運(yùn)行下面的代碼块仆。耗時(shí)最短的是1000ms
; -
asyncFunction total executing::5006.276123046875ms
這是最后的統(tǒng)計(jì)總共運(yùn)行時(shí)間代碼王暗。三個(gè)await
之后的異步過程之和1000(獨(dú)立的) + 2000(Promise.all) + 1000(Promise.race) = 5000ms
這個(gè)和統(tǒng)計(jì)出來的5006.276123046875ms
非常接近悔据。說明上面的異步過程,和同步代碼執(zhí)行過程一致俗壹,協(xié)程真的是在等待異步過程執(zhí)行完畢科汗。 -
asyncFunction done.
這個(gè)是async
函數(shù)返回的信息,在執(zhí)行時(shí)的then
函數(shù)中獲得绷雏,說明整個(gè)流程完畢之后參數(shù)傳遞的過程头滔。
異常處理
-
async
標(biāo)注過的函數(shù),返回一個(gè)Promise
對(duì)象涎显,采用.then().catch()
的方式來進(jìn)行異常處理坤检,是非常自然的方法,也推薦這么做期吓。就像上面的step3
那樣做早歇。 - 另外一種方法,就是對(duì)于異步過程采用
await
關(guān)鍵字讨勤,采用同步的try{} catch(){}
的方式來進(jìn)行異常處理箭跳。 - 這里要注意的是
await
關(guān)鍵字只能用在async
標(biāo)注的函數(shù)中,所以潭千,原來的函數(shù)谱姓,不管以前是同步的還是異步的,都要加上async
關(guān)鍵字刨晴,比如componentDidMount()
就要變?yōu)?code>async componentDidMount()才可以在內(nèi)部使用await
關(guān)鍵字逝段,不過功能上沒有任何影響。 - 另外割捅,采用同步的
try{} catch(){}
的方式,可以把同步帚桩,異步代碼都可以放在里面亿驾,有錯(cuò)誤都能抓到,比如null.length
這種账嚎,也能抓到莫瞬。
async componentDidMount() { // 這是React Native的回調(diào)函數(shù)儡蔓,加個(gè)async關(guān)鍵字,沒有任何影響疼邀,但是可以用await關(guān)鍵字
// 將異步和同步的代碼放在一個(gè)try..catch中喂江,異常都能抓到
try {
let array = null;
let data = await asyncFunction(); // 這里用await關(guān)鍵字,就能拿到結(jié)果值旁振;否則获询,沒有await的話,只能拿到Promise對(duì)象
if (array.length > 0) { // 這里會(huì)拋出異常拐袜,下面的catch也能抓到
array.push(data);
}
} catch (error) {
alert(JSON.stringify(error))
}
}
這里模擬的是網(wǎng)絡(luò)過程吉嚣。一般情況,
array
是一個(gè)數(shù)組蹬铺,用if (array.length > 0)
判斷一下長(zhǎng)度尝哆,有值再處理,沒有問題甜攀。但是秋泄,一旦網(wǎng)絡(luò)出問題,array
就是一個(gè)null
规阀,平時(shí)工作很好的if (array.length > 0)
判斷就會(huì)拋異常恒序,JS代碼就中斷,停止工作姥敛,會(huì)帶來意想不到的問題奸焙。這里加了一個(gè)
try..catch
結(jié)構(gòu),這種異常就能捕獲彤敛,(這是同步代碼中的異常与帆,不能用.then().catch()
抓到),根據(jù)異常信息,一般是null
沒有length
屬性墨榄,方便定位問題玄糟。這里的話用if (array && (array.length > 0))
就會(huì)安全一點(diǎn)。