溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

vue的async-await怎么使用

發(fā)布時(shí)間:2022-10-24 13:52:52 來源:億速云 閱讀:132 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“vue的async-await怎么使用”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“vue的async-await怎么使用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。

曾經(jīng)見過為了讓鉤子函數(shù)的異步代碼可以同步執(zhí)行,而對(duì)鉤子函數(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 的代碼最后會(huì)輸出:

start created
start mounted
undefined
end mounted
mounted cost: 2.88623046875ms
{__ob__: Observer}
end created
created cost: 3171.545166015625ms

很明顯沒有達(dá)到預(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 => ...

會(huì)寫出以上代碼的原因我估計(jì)有兩個(gè):

exp-01

正文

剖析一下

前言中針對(duì)代碼的執(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

對(duì)比實(shí)際的打印和期望的打印,就知道問題出在created鉤子內(nèi)使用了await的異步代碼,并沒有達(dá)到我們期望的那種的“異步代碼同步執(zhí)行”的效果,僅僅是一定程度上達(dá)到了這個(gè)效果。

下面來分析一下為什么會(huì)出現(xiàn)這個(gè)非預(yù)期的結(jié)果!

在分析前,讓我們來回顧一下一些javascript的基礎(chǔ)知識(shí)!看看下面這段代碼:

(function __main() {
 console.log('start');
 setTimeout(() => {
 console.log('console in setTimeout');
 }, 0);
 console.log('end');
})()

// output
start
end
console in setTimeout

這個(gè)打印順序有沒有讓你想到什么?!

任務(wù)隊(duì)列!

我們都知道JavaScript的代碼可以分成兩類:

同步代碼 和 異步代碼

同步代碼會(huì)在主線程按照編寫順序執(zhí)行;

異步代碼的觸發(fā)過程(注意是觸發(fā),比如異步請(qǐng)求的發(fā)起,就是在主線程同步觸發(fā)的)是同步的,但是異步代碼的實(shí)際處理邏輯(回調(diào)函數(shù))則會(huì)在異步代碼有響應(yīng)時(shí)將處理邏輯代碼推入任務(wù)隊(duì)列(也叫事件隊(duì)列),瀏覽器會(huì)在主線程(指當(dāng)前執(zhí)行環(huán)境的同步代碼)代碼執(zhí)行完畢后以一定的周期檢測(cè)任務(wù)隊(duì)列,若有需要處理的任務(wù),就會(huì)讓隊(duì)頭的任務(wù)出隊(duì),推入主線程執(zhí)行。

比如現(xiàn)在我們發(fā)起一個(gè)異步請(qǐng)求:

// 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');

在主線程中,大概首先會(huì)發(fā)生如下過程:

// exp-03
// step 1
console.log('start');

// step 2
axios.get('http://xxx.com/getList'); // 此時(shí)回調(diào)函數(shù)(即then內(nèi)部的邏輯)還沒有被調(diào)用

// step 3
console.log('end');

在看看瀏覽器此時(shí)在干什么!

此時(shí)事件輪詢(Event Loop)登場(chǎng),其實(shí)并非此時(shí)才登場(chǎng),而是一直都在!

“事件輪詢”這個(gè)機(jī)制會(huì)以一定的周期檢測(cè)任務(wù)隊(duì)列有沒有可執(zhí)行的任務(wù)(所謂任務(wù)其實(shí)就是callback),有即出隊(duì)執(zhí)行。

當(dāng) step 2 的請(qǐng)求有響應(yīng)了,異步請(qǐng)求的回調(diào)函數(shù)就會(huì)被添加到任務(wù)隊(duì)列(Task Queue)或者 稱為 事件隊(duì)列(Event Queue),然后等到事件輪詢的下一次檢測(cè)任務(wù)隊(duì)列,隊(duì)列里面任務(wù)就會(huì)依次出隊(duì),進(jìn)入主線程執(zhí)行:即執(zhí)行下面的代碼:

// 假定沒有出錯(cuò)的話
((resp) => {
 console.log('handle response');
})()

到此,簡(jiǎn)短科普了任務(wù)隊(duì)列的機(jī)制,聯(lián)想 exp-01 的代碼,大概知道出現(xiàn)非預(yù)期結(jié)果的原因了吧!

created鉤子中的await函數(shù),雖然是在一定程度上是同步的,但是他還是被掛起了,實(shí)際的處理邏輯(this.list =resp.xxx)則在響應(yīng)完成后才被添加進(jìn)任務(wù)隊(duì)列,并且在主線程的同步代碼執(zhí)行完畢后執(zhí)行。 下面是將延時(shí)時(shí)間設(shè)為0后的打?。?br/>

start created
start mounted
undefined
end mounted
mounted cost: 2.88623046875ms
{__ob__: Observer}
end created
created cost: 9.76611328125ms

這側(cè)面說明了await函數(shù)確實(shí)被被掛起,回調(diào)被添加到任務(wù)隊(duì)列,在主線程代碼執(zhí)行完畢后等待執(zhí)行。

然后是為什么說 exp-01 的代碼是一定程度的同步呢?!

同步執(zhí)行的另一個(gè)意思是不是就是:阻塞當(dāng)前線程的繼續(xù)執(zhí)行直到當(dāng)前邏輯執(zhí)行完畢~

看看 exp-01 的打印:

{__ob__: Observer}
end created
created cost: 3171.545166015625ms

end created 這句打印,是主線程的代碼,如果是一般的異步請(qǐng)求的話,這句打印應(yīng)該是在 {__ob__: Observer} 這句打印之前的yo,至于為什么會(huì)這樣,這里就不多解析,自行g(shù)oogle!

另外,這里來個(gè)小插曲,你應(yīng)該注意到,我一直強(qiáng)調(diào),回調(diào)函數(shù)被添加進(jìn)任務(wù)隊(duì)列的時(shí)機(jī)是在響應(yīng)完成之后,沒錯(cuò)確實(shí)如此的!

但在不清除這個(gè)機(jī)制前,你大概會(huì)有兩種猜想:

1.在觸發(fā)異步代碼的時(shí),處理邏輯就會(huì)被添加進(jìn)任務(wù)隊(duì)列;
2.上面說到的,在異步代碼響應(yīng)完成后,處理邏輯才會(huì)被添加進(jìn)任務(wù)隊(duì)列;

其實(shí)大可推斷一下

隊(duì)列的數(shù)據(jù)結(jié)構(gòu)特征是:先進(jìn)先出(First in First out)

此時(shí)假如主線程中有兩個(gè)異步請(qǐng)求如下:

// exp-04
syncRequest01(callback01);
syncRequest02(callback02);

假設(shè)處理機(jī)制是第一點(diǎn)描述那樣,那么callback01就會(huì)先被添加進(jìn)任務(wù)隊(duì)列,然后是callback02。

然后,我們?cè)偌僭O(shè)syncRequest01的響應(yīng)時(shí)間是10s,syncRequest02的響應(yīng)時(shí)間是5s。

到這里,有沒有察覺到違和感!

異步請(qǐng)求的實(shí)際表現(xiàn)是什么?是誰快誰的回調(diào)先被執(zhí)行,對(duì)吧!那么實(shí)際表現(xiàn)就是callback02會(huì)先于callback01執(zhí)行!

那么基于這個(gè)事實(shí),再看看上面的假設(shè)(callback01會(huì)執(zhí)行)~

ok!插曲完畢!

解法

首先讓我回顧一下目的,路由組件對(duì)異步請(qǐng)求返回的數(shù)據(jù)有強(qiáng)依賴,因此希望阻塞組件的渲染流程,待到異步請(qǐng)求響應(yīng)完畢之后再執(zhí)行。

這就是我們需要做的事情,需要強(qiáng)調(diào)的一點(diǎn)是: 我們對(duì)數(shù)據(jù)有強(qiáng)依賴 ,言外之意就是數(shù)據(jù)沒有按預(yù)期返回,就會(huì)導(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ā)這個(gè)鉤子后,主線程都會(huì)阻塞,頁面會(huì)一直保持假死狀態(tài),直到在調(diào)用 beforeRouterEnter 的回調(diào)函數(shù) next ,才會(huì)跳轉(zhuǎn)路由進(jìn)行新路由組件的渲染。

看起這個(gè)解決方案相當(dāng)適合上面我們提出的需求,在調(diào)用 next 前,就可以去拉取數(shù)據(jù)!

但是如剛剛說到的,頁面在一直假死,加入數(shù)據(jù)獲取花費(fèi)時(shí)間過長就難免變得很難看,用戶體驗(yàn)未免太差

為此,在 exp-05 中我在請(qǐng)完成前后分別調(diào)用了 this.showLoading() 和 this.hideLoading() 以便頁面 keep-alive 。

這個(gè)處理假死的loading有沒有讓你想到寫什么,沒錯(cuò)就是下面這個(gè)github跳轉(zhuǎn)頁面是頂部的小藍(lán)條

想想就有點(diǎn)cool,當(dāng)然還有很多的實(shí)現(xiàn)方式提升用戶體驗(yàn),比如作為body子元素的全屏loading,或者button-loading等等……

當(dāng)然,我們知道阻塞主線程怎么都是阻塞了,loading只是一種自欺欺人式的優(yōu)化(此時(shí)這個(gè)成語可不是什么貶義的詞語)!

因此,不是對(duì)數(shù)據(jù)有非常強(qiáng)的依賴,都應(yīng)在路由的鉤子進(jìn)行數(shù)據(jù)抓取,這樣就可以讓用戶“更快”地跳轉(zhuǎn)到目的頁。為避免頁面對(duì)數(shù)據(jù)依賴拋出的異常(大概就是 undefined of xxx ),我們可以對(duì)初始數(shù)據(jù)進(jìn)行一些預(yù)設(shè),比如 exp-01 中對(duì) this.list.rows 的依賴,我們可以預(yù)設(shè) this.list :

list: {
 rows: []
}

這樣就不會(huì)拋出異常,待到異步請(qǐng)求完成,基于vue的update機(jī)制二次渲染我們的預(yù)期數(shù)據(jù)~

讀到這里,這篇“vue的async-await怎么使用”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI