我們知道,javascript是單線程的,任務(wù)只能依次排開,,挨個執(zhí)行,但是為了完成某些并行的需求,類似網(wǎng)絡(luò)請求(網(wǎng)絡(luò)請求有單獨的線程去處理),就必須引入異步的概念,簡單來理解就是:我想要吃飯,但是做好飯需要十分鐘,這十分鐘我不能待著,我要玩手機,等飯做好了,我再繼續(xù)吃飯。
最傳統(tǒng)普遍的異步解決方案就是寫回調(diào)函數(shù),
定時器
setTimeout(function(){
console.log("開始吃飯")
},600000)
網(wǎng)絡(luò)請求
$post("www.chifan.com",function(res){
console.log("吃飯")
})
這會產(chǎn)生幾個問題
回調(diào)地獄,難處理的異常,非常不優(yōu)雅的代碼。。
隨著前端應(yīng)用復(fù)雜性的不斷提升,回調(diào)函數(shù)越來越不能滿足我們的需求,幾種解決方案被提了出來,
Promise,Generator,async/await
Promise
new Promise(function(resolve,reject){
if("做好飯"){
resolve("飯好了")
}
else{
reject("飯糊了")
}
})
.then(res=>{
console.log("吃飯")
})
.catch(err=>{
console.log("吃不成了")
})
Generator
function* chifan(){
yeild 'chifan'
}
new chifan().next()
async/await
async function(){
await zuofan();
return "吃飯了"
}
下面我們著重來介紹下promise,因為promise + async await 是業(yè)界異步解決方案最優(yōu)雅的解法了,大約只有c# 和 nodejs 原生實現(xiàn)了此特性
我們先看下PromiseA+規(guī)范
- "promise"是一個函數(shù),攜帶有滿足規(guī)范的then方法
- "promise" 有三種狀態(tài),
pending,onFulfilled,onRejected,且從pending只能切換到onFulfilled或者onRejected一種狀態(tài),不能切換 - then方法返回的也是promise,且可以鏈?zhǔn)秸{(diào)用
看懂了規(guī)范我們先來演示一下在promise 和 async await下我們?nèi)绾谓鉀Q異步問題。
比如我們想要在兩秒之后打印吃飯,吃完飯三秒后打印睡覺,傳統(tǒng)回調(diào)寫法
setTimeout(function(){
console.log('吃飯');
setTimeout(function(){
console.log('睡覺')
},3000)
},2000)
promise 寫法
new Promise(function(res,rej){
setTimeout(function(){
console.log("吃飯")
res()
},2000)
})
.then(res=>{
return new Promise(function(res,rej){
setTimeout(function(){
console.log("睡覺")
res()
},3000)
})
})
.then(res=>{
},
reject=>{
console.log("異常")
})
.catch(err=>{
console.log(err)
})
async await 寫法
(async function(){
await new Promise(function(res,rej){
setTimeout(function(){
console.log("吃飯")
res()
},2000)
})
await new Promise(function(res,rej){
setTimeout(function(){
console.log("睡覺")
res()
},3000)
})
})()
是不是感覺越寫越像同步呢。
async 關(guān)鍵字用于匿名函數(shù)前面或者函數(shù)名字前面,有了這個關(guān)鍵字,函數(shù)內(nèi)部可以使用await關(guān)鍵字,await關(guān)鍵字后面跟著一個promise對象,可以是函數(shù)返回一個promsie對象或者new 的一個promise對象,而每個await對應(yīng)promise之后的代碼,都會在當(dāng)前await對應(yīng)的promise resolve 之后才會執(zhí)行,相當(dāng)于將當(dāng)前await 對應(yīng)promise之后的代碼包裹到了then 之中執(zhí)行,,然后如果又碰到await。還是同理,一層層包裹,保證了多個await 對應(yīng)promise的順序執(zhí)行
現(xiàn)在async中的代碼可以順序執(zhí)行了,但是當(dāng)前加了async 關(guān)鍵字的函數(shù)還是無法保證其他的函數(shù)在此函數(shù)執(zhí)行完之后執(zhí)行,因為瀏覽器的事件循環(huán)機制中,promise是放在微任務(wù)隊列中執(zhí)行的,,
因此,async 函數(shù)的返回值也是一個promise,無法保證當(dāng)前async 函數(shù)后面的函數(shù)在此函數(shù)所有內(nèi)容執(zhí)行完之后執(zhí)行,如果多個async 函數(shù)并行執(zhí)行,我們還是需要將它當(dāng)成promsie 進行處理。
也就是說 async 的返回值是一個promise
這時候如果我們在es5 提供的數(shù)組的遍歷函數(shù)中使用async await 會產(chǎn)生一些難以預(yù)料的問題,如果你對async await promise 非常了解了,可以在 forEach,map, filter等中使用,類似這種
[1,2,3].map(async _=>{
await new Promise();
return _+1
})
// 執(zhí)行其他代碼
這個時候的函數(shù)執(zhí)行時序會有些復(fù)雜,如果你理解了本文,可以預(yù)見到執(zhí)行的結(jié)果,
如果你不是非常明白他的執(zhí)行時序,建議遇到循環(huán)時,還是乖乖的寫for循環(huán)吧。。。