一、為何會有Promise懂盐?
在JavaScript的世界中褥赊,所有代碼都是單線程執(zhí)行的。由于這個“缺陷”莉恼,導(dǎo)致JavaScript的所有網(wǎng)絡(luò)操作拌喉,瀏覽器事件翼岁,都必須是異步執(zhí)行。在Promise出現(xiàn)之前是通過回調(diào)函數(shù)(callback)實現(xiàn)異步, 簡單說回調(diào)函數(shù)就是將一個方法func2作為參數(shù)傳入另一個方法func1中司光,當(dāng)func1執(zhí)行到某一步或者滿足某種條件的時候才執(zhí)行傳入的參數(shù)func2琅坡。
一般來說我們會碰到的回調(diào)嵌套都不會很多,一般就一到兩級残家,但是某些情況下榆俺,回調(diào)嵌套很多時,代碼就會非常繁瑣坞淮,邏輯已經(jīng)很難理清楚了茴晋,這種情況俗稱——回調(diào)地獄。
多層嵌套的回調(diào)中回窘,有同步/異步的方法诺擅,那么執(zhí)行順序會變得混亂,而Promise采用鏈?zhǔn)秸{(diào)用啡直,使我們的代碼更容易理解和維護(hù)烁涌,而且Promise還增加了許多有用的特性,讓我們處理異步編程得心應(yīng)手酒觅。如下面這個例子:
//回調(diào)函數(shù)寫法:
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// ...
});
});
});
});
//Promise的寫法:
(new Promise(step1))
.then(step2)
.then(step3)
.then(step4);
二撮执、什么是Promise?
- Promise由社區(qū)最早提出和實現(xiàn),ES6將其寫進(jìn)了語言標(biāo)準(zhǔn),統(tǒng)一了語法呢铆,原生提供了Promise。
- Promise是抽象異步處理對象以及對其進(jìn)行各種操作的組件谋币。Promise就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果症概。
- Promise就如它的意思“承諾”一樣蕾额,既然執(zhí)行了,就不管程序運行結(jié)果成功還是失敗穴豫,最終都會給一個答復(fù)凡简,而這個答復(fù)就是一個Promise對象。執(zhí)行流程如下圖:
Promise對象有三種狀態(tài):
1、異步操作"未完成"(pending)
2帜乞、異步操作"已完成" (resolved)
3司抱、異步操作"失敗" (rejected)
這三種狀態(tài)的變化途徑:
1、異步操作從"未完成"到"已完成"
2黎烈、異步操作從"未完成“到"失敗"
Promise對象的最終結(jié)果:
1习柠、異步操作成功 Promise對象傳回一個值匀谣,狀態(tài)變?yōu)閞esolved
2、異步操作失敗 Promise對象拋出一個錯誤资溃,狀態(tài)變?yōu)閞ejected
注意:有且只有這2種結(jié)果武翎,并且狀態(tài)一旦改變,就無法再次改變狀態(tài)溶锭,這也是它名字“承諾”的由來宝恶。
三、基本用法
1趴捅、先聲明一個Promise對象垫毙,生成Promise實例。
let p = new Promise((resolve, reject) => {
// ...
if (/* 異步操作成功 */){
resolve(value);//成功拋出value
} else {
reject(error);//失敗拋出錯誤
}
});
2拱绑、Promise實例生成以后综芥,可以用then方法分別指定resolved狀態(tài)和reject狀態(tài)的回調(diào)函數(shù)。
p.then((value) => {
// 成功執(zhí)行
}, (error) => {
// 失敗執(zhí)行 --->可選
});
注意:實例化的Promise對象會立即執(zhí)行
現(xiàn)在我們可以開始寫一個簡單的例子猎拨,比如隨機(jī)生成一個數(shù)膀藐,1秒后判斷可不可以做除數(shù)(0不能做除數(shù)),代碼如下:
let p = new Promise((resolve, reject) => {
var divisor = Math.random();
setTimeout(() => {
if(divisor) {
resolve('可以做除數(shù)~');
}
else {
reject('不可以做除數(shù)~');
}
}, 1000);
});
console.log(p); // 在瀏覽器的控制臺運行的話红省,它返回的是一個包含了許多屬性的Promise對象
p.then((result) => {
console.log(result);
}, (err) => {
console.log(err);
}); // 1s后這里的輸出可能是fail也可能是success
四消请、常見方法
1、Promise.then()
promise.then(
() => { console.log('this is success callback') },
() => { console.log('this is fail callback') }
)
.then()方法是Promise原型鏈上的方法类腮,它包含兩個參數(shù)方法臊泰,分別是已成功resolved的回調(diào)和已失敗rejected的回調(diào)。
2蚜枢、Promise.catch()
promise.then(
() => { console.log('this is success callback') }
).catch(
(err) => { console.log(err) }
)
.catch()的作用是捕獲Promise的錯誤缸逃,與then()的rejected回調(diào)作用幾乎一致。但是由于Promise的拋錯具有冒泡性質(zhì)厂抽,能夠不斷傳遞需频,這樣就能夠在下一個catch()中統(tǒng)一處理這些錯誤。
同時catch()也能夠捕獲then()中拋出的錯誤筷凤,所以建議不要使用then()的rejected回調(diào)昭殉,而是統(tǒng)一使用catch()來處理錯誤。
同樣藐守,catch()中也可以拋出錯誤挪丢,由于拋出的錯誤會在下一個catch中被捕獲處理,因此可以再添加catch()卢厂。
3乾蓬、Promise.all()
var promise = Promise.all( [p1, p2, p3] )
promise.then(
// ...
).catch(
// ...
)
當(dāng)p1、p2慎恒、p3的狀態(tài)都變成resolved時任内,promise才會變成resolved撵渡,并調(diào)用then()的已完成回調(diào),但只要有一個變成rejected狀態(tài)死嗦,promise就會立刻變成rejected狀態(tài)趋距。
4、Promise.race()
var promise = Promise.race( [p1, p2, p3] )
promise.then(
// ...
).catch(
// ...
)
race()方法越除,參數(shù)與Promise.all()相同节腐,不同的是,參數(shù)中的p1廊敌、p2铜跑、p3只要有一個改變狀態(tài),promise就會立刻變成相同的狀態(tài)并執(zhí)行對于的回調(diào)骡澈。
5锅纺、Promise.done()
Promise.done()的用法類似.then() ,可以提供resolved和rejected方法肋殴,也可以不提供任何參數(shù)囤锉,它的主要作用是在回調(diào)鏈的尾端捕捉前面沒有被.catch() 捕捉到的錯誤。
6护锤、Promise.finally()
Promise. finally()接受一個方法作為參數(shù)官地,這個方法不管promise最終的狀態(tài)是怎樣,都一定會被執(zhí)行烙懦。
五驱入、缺點
說了這么多Promise的好用之處,那是不是堪稱完美的呢氯析?不是的亏较,下面列舉了它的幾條缺點,也是我們在使用過程中需要注意的地方掩缓。
- 無法取消Promise雪情,一旦新建它就會
立即執(zhí)行
,無法中途取消你辣。 - 如果不設(shè)置回調(diào)函數(shù)巡通,Promise內(nèi)部拋出的錯誤,不會反應(yīng)到外部舍哄。
- 當(dāng)處于Pending狀態(tài)時宴凉,無法得知目前進(jìn)展到哪一個階段(剛剛開始還是即將完成)。
六蠢熄、演變(async/await)
Promise讓我們告別回調(diào)函數(shù)跪解,寫出更優(yōu)雅的異步代碼;在實踐過程中签孔,卻發(fā)現(xiàn)Promise并不完美叉讥;技術(shù)進(jìn)步是無止境的,這時饥追,便有了async/await图仓。
使用async/await,搭配promise但绕,可以通過編寫形似同步的代碼來處理異步流程救崔,提高代碼的簡潔性和可讀性。
async
使用async function
可以定義一個異步函數(shù)捏顺,不管在函數(shù)體內(nèi)return了什么值六孵,async函數(shù)的實際返回值總是一個Promise
對象。
await
await操作符用于等待一個
Promise
對象幅骄,它只能在異步函數(shù)async function
內(nèi)部使用劫窒。
需要注意的是,await
表達(dá)式會暫停當(dāng)前async function
的執(zhí)行拆座,等待Promise
處理完成主巍。若Promise
正常處理,其處理結(jié)果作為await
表達(dá)式的值挪凑,繼續(xù)執(zhí)行async function
孕索;若Promise
處理異常,await
表達(dá)式會把Promise
的異常原因拋出。
相信大家都已經(jīng)在項目中使用過async/await語法了躏碳,那么我就不介紹它的用法了搞旭,直接說說需要注意哪些地方。
- 避免直接將await調(diào)用當(dāng)作變量菇绵,例如:
// 錯誤寫法
initData(await getData());
// 正確寫法
const data = await getData();
initData(data);
// 錯誤寫法
const obj = {
data: await getData()
}
// 正確寫法
const data = await getData();
const obj = {
data
}
- await只能用于async聲明的函數(shù)上下文中肄渗, 例如:
/* 場景:多個專題獲取并處理報告內(nèi)容 */
// 錯誤寫法
async handleInited() {
this.reportData.map(v => {
const reportData = await this.handleReport(v.topicId);
return reportData;
});
},
// 正確寫法
handleInited() {
this.reportData.map(async v => { // 從語法上來說,給map的callback加上async才可以運行
const reportData = await this.handleReport(v.topicId);
return reportData;
});
},
- 注意異步操作的依賴關(guān)系脸甘,避免濫用async/await恳啥,例如:
/* 場景:多個專題獲取并處理報告內(nèi)容 */
// 錯誤寫法
handleInited() {
this.reportData.map(async v => {
const reportData = await this.handleReport(v.topicId);
return reportData;
});
this.showLoading = false; // 并不會等待上面代碼返回結(jié)果后再執(zhí)行
},
// 正確寫法
async handleInited() {
Promise.all(this.reportData.map(v => {
return this.handleReport(v.topicId);
})).then(() => {
this.showLoading = false;
});
},
從上面的代碼可以看出,我們是想要handleReport函數(shù)并行執(zhí)行丹诀,等到都返回結(jié)果后再將showLoading置為false钝的。如果使用async/await
,顯然不能達(dá)到我們想要的铆遭,await
只會暫停map
的callback
硝桩,因此map
完成時,不能保證handleReport也全部完成枚荣,這時使用之前提到的Promise.all()
再合適不過了碗脊。
七、總結(jié)
JavaScript的異步編寫方式橄妆,從回調(diào)函數(shù)到Promise再到async/await衙伶,表面上只是寫法的變化祈坠,本質(zhì)上則是語言層的一次次抽象,讓我們可以用更簡單的方式實現(xiàn)同樣的功能矢劲,而不需要去考慮代碼是如何執(zhí)行的赦拘。