您好,登錄后才能下訂單哦!
本篇文章為大家展示了vue中async-await如何使用,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
曾經(jīng)見過為了讓鉤子函數(shù)的異步代碼可以同步執(zhí)行,而對鉤子函數(shù)使用async/await,就好像下面的代碼:
// exp-01 export default { async created() { const timeKey = 'cost'; console.time(timeKey); console.log('start created'); this.list = await this.getList(); console.log(this.list); console.log('end created'); console.timeEnd(timeKey); }, mounted() { const timeKey = 'cost'; console.time(timeKey); console.log('start mounted'); console.log(this.list.rows); console.log('end mounted'); console.timeEnd(timeKey); }, data() { return { list: [] }; }, methods: { getList() { return new Promise((resolve) => { setTimeout(() => { return resolve({ rows: [ { name: 'isaac', position: 'coder' } ] }); }, 3000); }); } } };
exp-01 的代碼最后會輸出:
start created
start mounted
undefined
end mounted
mounted cost: 2.88623046875ms
{__ob__: Observer}
end created
created cost: 3171.545166015625ms
很明顯沒有達到預(yù)期的效果,為什么?
根據(jù) exp-01 的輸出結(jié)果,可以看出代碼的執(zhí)行順序,首先是鉤子的執(zhí)行順序:
created => mounted
是的,鉤子的執(zhí)行順序還是正常的沒有被打亂,證據(jù)就是:created鉤子中的同步代碼是在mounted先執(zhí)行的:
start created start mounted
再看看created鉤子內(nèi)部的異步代碼:
this.list = await this.getList();
可以看見this.list的打印結(jié)果
end mounted mounted cost: 2.88623046875ms // 這是created鉤子打印的this.list {__ob__: Observer} end created
在mounted鉤子執(zhí)行完畢之后才打印,言外之意是使用async/await的鉤子內(nèi)部的異步代碼并沒有起到阻塞鉤子主線程的執(zhí)行。這里說的鉤子函數(shù)的主線程是指:
beforeCreate => created => beforeMount => mounted => ...
會寫出以上代碼的原因我估計有兩個:
exp-01
正文
剖析一下
前言中針對代碼的執(zhí)行流程分析了一下,很明顯沒有如期望的順序執(zhí)行,我們先來回顧一下期望的順序是什么
// step 1 created() { // step 1.1 let endTime; const startTime = Date.now(); console.log(`start created: ${startTime}ms`); // step 1.2 this.list = await this.getList(); endTime = Date.now(); console.log(this.list); console.log(`end created: ${endTime}ms, cost: ${endTime - startTime}ms`); }, // step 2 mounted() { let endTime; const startTime = Date.now(); console.log(`start mounted: ${startTime}ms`); console.log(this.list.rows); endTime = Date.now(); console.log(`end mounted: ${endTime}ms, cost: ${endTime - startTime}ms`); } // step 1 => step 1.1 => step 1.2 => step 2
期望的打印結(jié)果是:
// step 1(created) start created // this.list {__ob__: Observer} end created created cost: 3171.545166015625ms // step 2(mounted) start mounted // this.list.rows [{…}, __ob__: Observer] end mounted mounted cost: 2.88623046875ms
對比實際的打印和期望的打印,就知道問題出在created鉤子內(nèi)使用了await的異步代碼,并沒有達到我們期望的那種的“異步代碼同步執(zhí)行”的效果,僅僅是一定程度上達到了這個效果。
下面來分析一下為什么會出現(xiàn)這個非預(yù)期的結(jié)果!
在分析前,讓我們來回顧一下一些javascript的基礎(chǔ)知識!看看下面這段代碼:
(function __main() { console.log('start'); setTimeout(() => { console.log('console in setTimeout'); }, 0); console.log('end'); })() // output start end console in setTimeout
這個打印順序有沒有讓你想到什么?!
任務(wù)隊列!
我們都知道JavaScript的代碼可以分成兩類:
同步代碼 和 異步代碼
同步代碼會在主線程按照編寫順序執(zhí)行;
異步代碼的觸發(fā)過程(注意是觸發(fā),比如異步請求的發(fā)起,就是在主線程同步觸發(fā)的)是同步的,但是異步代碼的實際處理邏輯(回調(diào)函數(shù))則會在異步代碼有響應(yīng)時將處理邏輯代碼推入任務(wù)隊列(也叫事件隊列),瀏覽器會在主線程(指當前執(zhí)行環(huán)境的同步代碼)代碼執(zhí)行完畢后以一定的周期檢測任務(wù)隊列,若有需要處理的任務(wù),就會讓隊頭的任務(wù)出隊,推入主線程執(zhí)行。
比如現(xiàn)在我們發(fā)起一個異步請求:
// exp-02 console.log('start'); axios.get('http://xxx.com/getList') .then((resp) => { console.log('handle response'); }) .catch((error) => { console.error(error); }); console.log('end');
在主線程中,大概首先會發(fā)生如下過程:
// exp-03 // step 1 console.log('start'); // step 2 axios.get('http://xxx.com/getList'); // 此時回調(diào)函數(shù)(即then內(nèi)部的邏輯)還沒有被調(diào)用 // step 3 console.log('end');
在看看瀏覽器此時在干什么!
此時事件輪詢(Event Loop)登場,其實并非此時才登場,而是一直都在!
“事件輪詢”這個機制會以一定的周期檢測任務(wù)隊列有沒有可執(zhí)行的任務(wù)(所謂任務(wù)其實就是callback),有即出隊執(zhí)行。
當 step 2 的請求有響應(yīng)了,異步請求的回調(diào)函數(shù)就會被添加到任務(wù)隊列(Task Queue)或者 稱為 事件隊列(Event Queue),然后等到事件輪詢的下一次檢測任務(wù)隊列,隊列里面任務(wù)就會依次出隊,進入主線程執(zhí)行:即執(zhí)行下面的代碼:
// 假定沒有出錯的話 ((resp) => { console.log('handle response'); })()
到此,簡短科普了任務(wù)隊列的機制,聯(lián)想 exp-01 的代碼,大概知道出現(xiàn)非預(yù)期結(jié)果的原因了吧!
created鉤子中的await函數(shù),雖然是在一定程度上是同步的,但是他還是被掛起了,實際的處理邏輯(this.list =resp.xxx)則在響應(yīng)完成后才被添加進任務(wù)隊列,并且在主線程的同步代碼執(zhí)行完畢后執(zhí)行。 下面是將延時時間設(shè)為0后的打印:
start created start mounted undefined end mounted mounted cost: 2.88623046875ms {__ob__: Observer} end created created cost: 9.76611328125ms
這側(cè)面說明了await函數(shù)確實被被掛起,回調(diào)被添加到任務(wù)隊列,在主線程代碼執(zhí)行完畢后等待執(zhí)行。
然后是為什么說 exp-01 的代碼是一定程度的同步呢?!
同步執(zhí)行的另一個意思是不是就是:阻塞當前線程的繼續(xù)執(zhí)行直到當前邏輯執(zhí)行完畢~
看看 exp-01 的打印:
{__ob__: Observer} end created created cost: 3171.545166015625ms
end created 這句打印,是主線程的代碼,如果是一般的異步請求的話,這句打印應(yīng)該是在 {__ob__: Observer} 這句打印之前的yo,至于為什么會這樣,這里就不多解析,自行g(shù)oogle!
另外,這里來個小插曲,你應(yīng)該注意到,我一直強調(diào),回調(diào)函數(shù)被添加進任務(wù)隊列的時機是在響應(yīng)完成之后,沒錯確實如此的!
但在不清除這個機制前,你大概會有兩種猜想:
1.在觸發(fā)異步代碼的時,處理邏輯就會被添加進任務(wù)隊列;
2.上面說到的,在異步代碼響應(yīng)完成后,處理邏輯才會被添加進任務(wù)隊列;
其實大可推斷一下
隊列的數(shù)據(jù)結(jié)構(gòu)特征是:先進先出(First in First out)
此時假如主線程中有兩個異步請求如下:
// exp-04 syncRequest01(callback01); syncRequest02(callback02);
假設(shè)處理機制是第一點描述那樣,那么callback01就會先被添加進任務(wù)隊列,然后是callback02。
然后,我們再假設(shè)syncRequest01的響應(yīng)時間是10s,syncRequest02的響應(yīng)時間是5s。
到這里,有沒有察覺到違和感!
異步請求的實際表現(xiàn)是什么?是誰快誰的回調(diào)先被執(zhí)行,對吧!那么實際表現(xiàn)就是callback02會先于callback01執(zhí)行!
那么基于這個事實,再看看上面的假設(shè)(callback01會執(zhí)行)~
ok!插曲完畢!
解法
首先讓我回顧一下目的,路由組件對異步請求返回的數(shù)據(jù)有強依賴,因此希望阻塞組件的渲染流程,待到異步請求響應(yīng)完畢之后再執(zhí)行。
這就是我們需要做的事情,需要強調(diào)的一點是: 我們對數(shù)據(jù)有強依賴 ,言外之意就是數(shù)據(jù)沒有按預(yù)期返回,就會導(dǎo)致之后的邏輯出現(xiàn)不可避免的異常。
接下來,我們就需要探討一下解決方案!
組件內(nèi)路由守衛(wèi)了解一下???
beforeRouteEnter
beforeRouteUpdate (2.2 新增)
beforeRouteLeave
這里需要用到的路由守衛(wèi)是: beforeRouterEnter , 先看代碼:
// exp-05 export default { beforeRouteEnter(to, from, next) { this.showLoading(); this.getList() .then((resp) => { this.hideLoading(); this.list = resp.data; next(); }) .catch((error) => { this.hideLoading(); // handle error }); }, mounted() { let endTime; const startTime = Date.now(); console.log(`start mounted: ${startTime}ms`); console.log(this.list.rows); endTime = Date.now(); console.log(`end mounted: ${endTime}ms, cost: ${endTime - startTime}ms`); }, };
路由守衛(wèi) beforeRouterEnter ,觸發(fā)這個鉤子后,主線程都會阻塞,頁面會一直保持假死狀態(tài),直到在調(diào)用 beforeRouterEnter 的回調(diào)函數(shù) next ,才會跳轉(zhuǎn)路由進行新路由組件的渲染。
看起這個解決方案相當適合上面我們提出的需求,在調(diào)用 next 前,就可以去拉取數(shù)據(jù)!
但是如剛剛說到的,頁面在一直假死,加入數(shù)據(jù)獲取花費時間過長就難免變得很難看,用戶體驗未免太差
為此,在 exp-05 中我在請完成前后分別調(diào)用了 this.showLoading() 和 this.hideLoading() 以便頁面 keep-alive 。
這個處理假死的loading有沒有讓你想到寫什么,沒錯就是下面這個github跳轉(zhuǎn)頁面是頂部的小藍條
想想就有點cool,當然還有很多的實現(xiàn)方式提升用戶體驗,比如作為body子元素的全屏loading,或者button-loading等等……
當然,我們知道阻塞主線程怎么都是阻塞了,loading只是一種自欺欺人式的優(yōu)化(此時這個成語可不是什么貶義的詞語)!
因此,不是對數(shù)據(jù)有非常強的依賴,都應(yīng)在路由的鉤子進行數(shù)據(jù)抓取,這樣就可以讓用戶“更快”地跳轉(zhuǎn)到目的頁。為避免頁面對數(shù)據(jù)依賴拋出的異常(大概就是 undefined of xxx ),我們可以對初始數(shù)據(jù)進行一些預(yù)設(shè),比如 exp-01 中對 this.list.rows 的依賴,我們可以預(yù)設(shè) this.list :
list: { rows: [] }
這樣就不會拋出異常,待到異步請求完成,基于vue的update機制二次渲染我們的預(yù)期數(shù)據(jù)~
小結(jié)
對于 exp-01 的寫法,也不能說他是錯誤或不好的寫法,凡事都要看我們是出于什么目的,如果僅僅是為了保證多個異步函數(shù)的執(zhí)行順序, exp-01 的寫法沒有任何錯誤,因此async/await不能用在路由鉤子上什么的并不存在!
上述內(nèi)容就是vue中async-await如何使用,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。