這段時(shí)間一直在重構(gòu)項(xiàng)目曲初,遇見很多請(qǐng)求“高并發(fā)”钮热,因?yàn)樯婕暗蕉鄠€(gè)請(qǐng)求又或者多個(gè)連續(xù)請(qǐng)求校辩。之所以給高并發(fā)帶上引號(hào)窘问,因?yàn)槊鎸?duì)大量請(qǐng)求的時(shí)候,我們需要調(diào)整好姿勢(shì)宜咒,怎樣去好好的去寫異步回調(diào)惠赫,弄清各個(gè)請(qǐng)求的順序,稍微不注意可能就掉坑了故黑,可能調(diào)試半天看著vue-devtool控制臺(tái)打印的自己以為的“完美的數(shù)據(jù)”儿咱,但是頁面各種顯示不聽話。哦场晶,那說明你掉坑了混埠!
理清好執(zhí)行的先后順序,其次再寫回調(diào)的時(shí)候我們就要選好正確的方法和正確的姿勢(shì)峰搪,這樣才不會(huì)造成將來你寫的代碼你認(rèn)不清的尷尬岔冀,同時(shí)感覺代碼一目了然凯旭!
同時(shí)考慮到異步回調(diào)我們需要理解一些知識(shí):
js的運(yùn)行機(jī)制:
在代碼運(yùn)行時(shí)會(huì)形成任務(wù)隊(duì)列概耻,分為同步任務(wù)隊(duì)列和異步任務(wù)對(duì)列,同步隊(duì)列優(yōu)先加載罐呼,異步任務(wù)隊(duì)列會(huì)形成隊(duì)列任務(wù)池鞠柄,定時(shí)器不會(huì)一下加載到異步任務(wù),而是在設(shè)定的時(shí)間后加載到異步任務(wù)嫉柴,即使設(shè)置為0厌杜,瀏覽器也有它的響應(yīng)時(shí)間,以前是10ms.現(xiàn)在是4ms.異步任務(wù)包括dom 事件计螺,定時(shí)器夯尽,promise
首先作為異步回調(diào)的功能和作用不去作過多解釋,對(duì)于js這種單線程異步回調(diào)是性能優(yōu)化的一些點(diǎn)登馒。
異步回調(diào)我覺得主要有兩方面作用:
- 不阻礙程序運(yùn)行匙握,將一些延時(shí)較久的函數(shù)異步執(zhí)行,不妨礙正常同步運(yùn)行的代碼陈轿;
- 一個(gè)函數(shù)必須在某個(gè)函數(shù)執(zhí)行完成后才能運(yùn)行圈纺,比如說需要用函數(shù)執(zhí)行完后的某些數(shù)據(jù)秦忿;
首先寫異步回調(diào)的姿勢(shì)大概有這樣幾種
- 直接函數(shù)套函數(shù)(通俗的講)
就像這樣
function fn(callback) {
setTimeout(() => {
callback()
}, 1000);
}
function f1() {
console.log('f1')
}
fn(f1)
// fn
// f1
這樣我們感覺還行,還不錯(cuò)蛾娶,還能接受灯谣,那如果有另外一個(gè)f2函數(shù),f1像fn那樣蛔琅,在多個(gè)f3:
function fn(callback) {
setTimeout(() => {
callback(f2)
}, 1000);
console.log('fn')
}
function f1(callback) {
setTimeout(() => {
callback(f3)
}, 1000);
console.log('f1')
}
function f2(callback) {
setTimeout(() => {
callback(f3)
}, 1000);
console.log('f2')
}
function f3() {
console.log('f3');
}
fn(f1)
// fn
// f1
// f2
//f3
這樣看著代碼也沒這么亂胎许,但是感覺把自己調(diào)懵了,如果想看出它的一些過程揍愁,我們將函數(shù)改寫一下
function fn(callback) {
setTimeout(() => {
callback((callback) => {
setTimeout(() => {
callback()
}, 1000);
console.log('f2')
})
}, 1000);
console.log('fn')
}
fn((callback) => {
setTimeout(() => {
callback((callback) => {
console.log('f3')
})
}, 1000);
console.log('f1')
})
// fn
// f1
// f2
// f3
如果連起來看呐萨,分清之前的f1,f2,f3函數(shù)很困難吧,這還只是三個(gè)回調(diào)函數(shù)莽囤,有時(shí)候不僅僅這些吧谬擦,如果再來兩個(gè),咋樣朽缎?惨远??
所以傳統(tǒng)方法晦澀難懂话肖。北秽。。最筒。
- 第二種方法看起來可能就比較爽了------Promise
Promise 是異步編程的一種解決方案贺氓,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件,更合理和更強(qiáng)大床蜘。它由社區(qū)最早提出和實(shí)現(xiàn)辙培,ES6 將其寫進(jìn)了語言標(biāo)準(zhǔn),統(tǒng)一了用法邢锯,原生提供了Promise對(duì)象扬蕊。
所謂Promise,簡(jiǎn)單說就是一個(gè)容器丹擎,里面保存著某個(gè)未來才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果尾抑。從語法上說,Promise 是一個(gè)對(duì)象蒂培,從它可以獲取異步操作的消息再愈。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進(jìn)行處理
前兩句話是摘自阮一峰老師的《es6入門》
我理解的Promise其實(shí)就是給你封裝好的一個(gè)異步對(duì)象护戳,本身有resolve和reject參數(shù)翎冲,當(dāng)然也是函數(shù),其實(shí)這里叫函數(shù)也不太好灸异,我們不如說叫“自定義鉤子”府适,當(dāng)然這個(gè)還不像我們常說的鉤子函數(shù)那樣羔飞。Promise對(duì)象外部暴露的了兩個(gè)分別對(duì)應(yīng)的是成功之后的then函數(shù),另一個(gè)是reject函數(shù)檐春。說白了這兩個(gè)函數(shù)是由用來設(shè)計(jì)resolve和reject函數(shù)的逻淌。Promise只負(fù)責(zé)將你寫的函數(shù)在內(nèi)部調(diào)用,同時(shí)將他的返回值有兩個(gè)(這里說的兩個(gè)是兩種結(jié)果)傳遞出來疟暖,成功之后自然就是resolve(data),失敗是reject(data)卡儒,當(dāng)然我們寫resolve的代碼塊其實(shí)就是Promise實(shí)例執(zhí)行完后對(duì)應(yīng)的then函數(shù)的執(zhí)行,當(dāng)然data就是then函數(shù)回調(diào)的參數(shù)俐巴,當(dāng)然reject和catch()也是一樣的道理骨望。其實(shí)看著這么像函數(shù)傳參的過程,也這么像“依賴注入”的趕腳欣舵。
不說別的了擎鸠,直接將上面函數(shù)改寫一下:
function fn() {
console.log('fn');
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000);
})
}
function f1() {
console.log('f1')
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000);
})
}
function f2() {
console.log('f2')
return new Promise(resolve2 => {
setTimeout(() => {
resolve2()
}, 1000);
})
}
function f3() {
console.log('f3');
}
fn().then(() => {
f1().then(() => {
f2().then(() => {
f3()
})
})
})
// fn
// f1
// f2
// f3
等到講aync和await時(shí)再好好講講Promise,今天的項(xiàng)目有一個(gè)特點(diǎn)就被埋坑了
有了Promise對(duì)象缘圈,就可以將異步操作以同步操作的流程表達(dá)出來劣光,避免了層層嵌套的回調(diào)函數(shù)。此外糟把,Promise對(duì)象提供統(tǒng)一的接口绢涡,使得控制異步操作更加容易,看著上面的代碼是不是清爽多了,但是感覺函數(shù)一多結(jié)構(gòu)看著似乎也不那么友好遣疯,同時(shí)Promise也有弊端就是獲取錯(cuò)誤信息的時(shí)候雄可,這些就不贅述了,網(wǎng)上應(yīng)該有很多介紹的缠犀,今天的主角是async 和 await
- async 和 await
這兩個(gè)方法其實(shí)就是Generator 函數(shù)的語法糖数苫。詳情請(qǐng)見阮一峰的es6
先說說背景,今天做一個(gè)黑名單的需求:
拉取軟件列表的時(shí)候夭坪,首先我需要對(duì)list中存在的黑名單來個(gè)請(qǐng)求黑名單的請(qǐng)求文判,同時(shí)請(qǐng)求完成后我們需要將黑名單list存儲(chǔ)在vue的data對(duì)象里过椎,然后再通過對(duì)list進(jìn)行過濾室梅,對(duì)是黑名單的item進(jìn)行軟件圖標(biāo)的置灰操作,同時(shí)在不同的分類我們還需要進(jìn)行判斷是否進(jìn)行過黑名單的操作疚宇,若果進(jìn)行過就可以對(duì)本分組進(jìn)行鎖定和解鎖操作亡鼠,同時(shí)后續(xù)操作我們還需要拉取一個(gè)已經(jīng)鎖定分組的集合,同時(shí)是判定當(dāng)前分組是否屬于已經(jīng)鎖定分組敷待,相應(yīng)的在頁面中對(duì)當(dāng)前分組是顯示解鎖按鈕還是鎖定按鈕
鑒于涉及到這么多的請(qǐng)求间涵,一個(gè)請(qǐng)求完成后需要完成幾個(gè)請(qǐng)求才能進(jìn)行相應(yīng)參數(shù),同時(shí)需要請(qǐng)求的先后順序很明確榜揖。
剛開始考慮就用Promise勾哩,因?yàn)榉庋b的ajax方法就是基于axios的抗蠢,但涉及到這么多連續(xù)請(qǐng)求先別說結(jié)構(gòu)不友好,可能出錯(cuò)你都不知道怎么出的思劳,所以我打算用async和await:
當(dāng)然咱們還是先把上面的那個(gè)例子改一下再說項(xiàng)目中的問題:
function f1() {
console.log('f1')
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000);
})
}
function f2() {
console.log('f2')
return new Promise(resolve2 => {
setTimeout(() => {
resolve2()
}, 1000);
})
}
function f3() {
console.log('f3');
}
async function fn() {
console.log('fn');
await f1()
await f2()
f3()
}
fn()
// fn
// f1
// f2
// f3
這樣寫代碼是不是看起來清爽多了迅矛!
今天寫邏輯的時(shí)候犯了一個(gè)影響智商的錯(cuò)看代碼和執(zhí)行效果
在公司寫的代碼不變貼上來,所以就來模擬一下函數(shù)的執(zhí)行
async function fn() {
await f1()
console.log('fn')
}
function f1() {
new Promise(resolve => {
setTimeout(() => {
console.log('f1')
resolve()
}, 1000);
})
}
fn()
// fn
// f1
async function fn() {
await f1()
console.log('fn')
}
function f1() {
return new Promise(resolve => {
setTimeout(() => {
console.log('f1')
resolve()
}, 1000);
})
}
fn()
// f1
// fn
看看兩次執(zhí)行的結(jié)果
第一個(gè)函數(shù)塊寫的時(shí)候完全不按異步執(zhí)行去操作潜叛,總是像一般函數(shù)那樣秽褒,await當(dāng)成了異步隊(duì)列,我只想說明注意的一個(gè)點(diǎn)事用Promise時(shí)的return威兜。
我們應(yīng)該了解下面這些事:
Promise有一個(gè)性質(zhì)就是立即執(zhí)行销斟,我們需要將它外包一層函數(shù)(就叫fn),當(dāng)我們await fn(),請(qǐng)記住fn只是為了不讓promise立即執(zhí)行,所以我們一定得在fn函數(shù)中返回promise椒舵,如果不返回蚂踊,應(yīng)該知道沒有返回值的函數(shù)的執(zhí)行結(jié)果是undefined,還執(zhí)行個(gè)毛線呀笔宿!
記住函數(shù)沒有返回值執(zhí)行結(jié)果就是undefinedc彩啤!4敕ァ特纤!
同時(shí)還有一個(gè)比較常見的問題:
async getList() {
fetchList(this.listQuery).then(
({data}) => {
let list = data.list
this.softTotalNum = data.total
// 獲取黑名單list
await this.getBlackList()
list.map(item => {
if (this.soft_ids.indexOf(item.soft_id) >= 0) {
item['is_hidden'] = 1
}
return item
})
this.list = list;
})
},
其實(shí)應(yīng)該這樣寫
getList() {
fetchList(this.listQuery).then(
async ({data}) => {
let list = data.list
this.softTotalNum = data.total
// 獲取黑名單list
await this.getBlackList()
list.map(item => {
if (this.soft_ids.indexOf(item.soft_id) >= 0) {
item['is_hidden'] = 1
}
return item
})
this.list = list;
})
},
一定要在你用的await的最近的父級(jí)用async聲明異步函數(shù),其他的頂級(jí)父級(jí)沒有必要寫async
今天遇到坑后總結(jié)了下面三點(diǎn):
- async是一個(gè)異步函數(shù)聲明詞侥加,await必須在async函數(shù)中使用捧存,await后面應(yīng)該你用一個(gè)延時(shí)函數(shù),當(dāng)然你用一個(gè)一般函數(shù)也行担败,就是立即執(zhí)行而已昔穴。根據(jù)單詞的字面意思,await我們可以理解為必須等我后面的函數(shù)執(zhí)行完之后提前,下面的代碼才能運(yùn)行吗货。
- await的最近父級(jí)必須是async函數(shù),否則會(huì)報(bào)“await is a reserved word”錯(cuò)狈网。
- await對(duì)應(yīng)的如果類似于promise的函數(shù)宙搬,鑒于promise的立即執(zhí)行的特點(diǎn),我們需要將它外包一層函數(shù)(就叫fn),當(dāng)我們await fn(),請(qǐng)記住fn只是問了不讓promise立即執(zhí)行拓哺,所以我們一定得在fn函數(shù)中返回promise勇垛,如果不返回,應(yīng)該知道沒有返回值的函數(shù)的執(zhí)行結(jié)果是undefined士鸥,還執(zhí)行個(gè)毛線呀闲孤!
以上就是一些按async和await的一些知識(shí),當(dāng)然這是es7的東西烤礁,記得babel編譯K匣7收铡!