1. 前言
JavaScript是一門(mén)單線(xiàn)程語(yǔ)言,在執(zhí)行一些比較耗時(shí)的操作(比如常見(jiàn)的Ajax請(qǐng)求)時(shí)湘换,為了不阻塞后面代碼的執(zhí)行蚀苛,往往需要執(zhí)行異步操作。關(guān)于JS的運(yùn)行機(jī)制糊识,大家可以看阮一峰的這篇文章:JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop
如何處理異步操作在一直是個(gè)值得關(guān)注的問(wèn)題绩社,我會(huì)在這篇文章里介紹幾種常見(jiàn)的處理異步函數(shù)的解決方案
2. 使用回調(diào)函數(shù)
如果你有使用過(guò)JQuery,那么肯定會(huì)熟悉這樣的處理方式赂苗,回調(diào)函數(shù)是一個(gè)作為變量傳遞給另外一個(gè)函數(shù)的函數(shù)愉耙,它在主體函數(shù)執(zhí)行完之后執(zhí)行。
let delayWithCallback = (time, callback) => {
console.log('handle...')
setTimeout(() => {
if (typeof callback === 'function') {
callback(`success`)
}
}, time)
}
在callback方法里處理回調(diào)
3. 使用Promise
Promise構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù)拌滋,該函數(shù)的兩個(gè)參數(shù)分別是resolve和reject朴沿,分別表示異步操作執(zhí)行成功后的回調(diào)函數(shù)和異步操作執(zhí)行失敗后的回調(diào)函數(shù)。這里說(shuō)的“成功”和“失敗”主要是為了便于理解败砂,準(zhǔn)確的說(shuō)法是resolve和reject改變了Promise的狀態(tài)赌渣,resolve將Promise的狀態(tài)置為resolved,reject將Promise的狀態(tài)置為rejected
let index = 1;
let delayWithPromise = (time) => {
return new Promise((resolve, reject) => {
console.log(`task${index} handle...`)
index++
setTimeout(() => {
resolve('success')
}, time)
})
}
3.1 在Promise實(shí)例上使用then方法里處理回調(diào)
then
方法是Promise原型上的方法昌犹,Promise.prototype.then()坚芜,then方法接受兩個(gè)參數(shù),第一個(gè)參數(shù)是Resolved狀態(tài)的回調(diào)函數(shù)祭隔,第二個(gè)參數(shù)(可選)是Rejected狀態(tài)的回調(diào)函數(shù)
let func2 = () => {
console.log('start')
delayWithPromise(1000).then(result => {
console.log(result)
console.log('end')
})
}
func2()
3.2 多個(gè)異步操作
假定有下面的異步方法货岭,用于獲取學(xué)生信息
let getJSON = (key) => {
const data = {
stu: 'stu1',
stu1: {
age: 1
},
stu2: {
age: 2
}
}
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve(data[key])
}, 100);
})
}
Promise.all方法用于將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例疾渴。此時(shí)多個(gè)Promise實(shí)例可以同步執(zhí)行千贯,返回值是一個(gè)數(shù)組,包含每個(gè)操作的結(jié)果
let func3 = () => {
Promise.all([
getJSON('stu1'),
getJSON('stu2')
]).then(stu => {
console.log(stu)
})
}
func3()
3.3 鏈?zhǔn)秸{(diào)用解決Promise異步嵌套問(wèn)題
在上面的代碼中搞坝,我們不關(guān)心操作執(zhí)行的順序搔谴,Promise.all方法可以很好的解決多個(gè)異步操作同時(shí)執(zhí)行的問(wèn)題; 如果當(dāng)前的異步操作依賴(lài)上一個(gè)操作的結(jié)果桩撮,就很容易寫(xiě)出func4()這樣的代碼敦第,在func4()中,嵌套的層級(jí)還比較少(2層)店量,如果有20芜果、30層呢,這樣代碼就難以維護(hù)了
let func4 = () => {
getJSON('stu').then(result1 => {
getJSON(result1).then(result2 => {
.....
})
})
}
func4()
如果我們?cè)趖hen方法中返回的是一個(gè)新的Promise實(shí)例融师,就可以形成鏈?zhǔn)秸{(diào)用關(guān)系了
采用then鏈?zhǔn)秸{(diào)用右钾,它避免了異步函數(shù)之間的層層嵌套,將原來(lái)異步函數(shù)的“嵌套關(guān)系”轉(zhuǎn)變?yōu)楸阌陂喿x和理解的“鏈?zhǔn)健辈襟E關(guān)系,可以指定一組按照次序調(diào)用的回調(diào)函數(shù)舀射,就像func5()窘茁,此時(shí)代碼的結(jié)構(gòu)會(huì)清晰很多
let func5 = () => {
getJSON('stu')
.then(result1 => {
return getJSON(result1)
}).then(result2 => {
console.log(result2)
})
}
func5()
4. 采用async/await解決異步問(wèn)題
async/await是ES7中的新特性,下面是關(guān)于async的幾個(gè)要點(diǎn):
- 在function前面加async關(guān)鍵字表示這是一個(gè)async函數(shù)
- async的返回值是一個(gè)Promise對(duì)象脆烟,你可以用then方法添加回調(diào)函數(shù)
- await后面跟著的應(yīng)該是一個(gè)promise對(duì)象山林,如果不是,會(huì)被轉(zhuǎn)成一個(gè)立即resolve的Promise對(duì)象
- await表示在這里等待promise返回結(jié)果了邢羔,再繼續(xù)執(zhí)行驼抹。
let func6 = async () => {
console.log('start')
let result = await delayWithPromise(1000);
console.log(result)
console.log('end')
}
func6()
4.1 async/await處理多個(gè)異步問(wèn)題
一個(gè)一個(gè)的執(zhí)行
let func7 = async () => {
console.log('start')
let result1 = await delayWithPromise(500)
let result2 = await delayWithPromise(500)
console.dir(result1, result2)
console.log('end')
}
func7()
同時(shí)執(zhí)行
let func8 = async () => {
console.log('start')
let [result1, result2] = await Promise.all([
delayWithPromise(500),
delayWithPromise(500)
])
console.dir(result1, result2)
}
func8()
func7()和func8()的在處理時(shí)會(huì)有些不同,在func7()里會(huì)先打印task1 handle...
拜鹤,500ms之后砂蔽,再打印task2 handle...
但是在func8()中,task1 handle...
和task2 handle...
是同時(shí)打印的署惯,這說(shuō)明在func7()任務(wù)是一個(gè)一個(gè)順序阻塞執(zhí)行的,在func8()是同時(shí)同步執(zhí)行的
總結(jié)一下镣隶,在含有多個(gè)異步操作的方法中,如果你的代碼邏輯里面存在相互依賴(lài)關(guān)系,比如當(dāng)前操作依賴(lài)上一個(gè)操作的結(jié)果升筏,那么你可以使用func7()這樣的寫(xiě)法
如果你的異步操作之間沒(méi)有依賴(lài)關(guān)系外构,你就應(yīng)該使用func8()這樣的寫(xiě)法,這樣前面的await不會(huì)阻塞后面的異操作域那,所有操作同時(shí)咙边,可以大大提高效率