在了解Promise之前需要了解js程序執(zhí)行機(jī)制.
我們都知道 JS 是一個(gè)單線(xiàn)程的語(yǔ)言刀脏,永遠(yuǎn)只有一個(gè)通道在運(yùn)行程序馆铁。通常來(lái)說(shuō)就是按書(shū)寫(xiě)順序執(zhí)行,先執(zhí)行程序A,再執(zhí)行程序B,這樣就會(huì)引出一個(gè)問(wèn)題
假如我有個(gè)圖片資源請(qǐng)求,請(qǐng)求時(shí)間很長(zhǎng),那么就會(huì)造成下面的代碼就要等這個(gè)圖片資源請(qǐng)求完成之后再執(zhí)行下一步程序,在這個(gè)過(guò)程中其他的程序不會(huì)被執(zhí)行,這樣是很不友好的.
還好程序員發(fā)明了異步和同步這兩種方式來(lái)處理問(wèn)題
異步執(zhí)行的好處
程序異步運(yùn)行,可以提高程序運(yùn)行的效率外厂,不必等一個(gè)程序跑完,再跑下一個(gè)程序誓焦,特別當(dāng)這兩個(gè)程序是無(wú)關(guān)的時(shí)候瓜贾。
模擬 JS 中異步的方法 —— setTimeout
function fn(){
console.log('1');
console.log('2');
}
function fn2() {
setTimeout(()=>{
console.log('3');
},1000)
console.log('4');
}
fn()
fn2()
//1
//2
//4
//3
這個(gè)例子中我們可以看到,我們定義了兩個(gè)函數(shù),fn()和fn2(),這里需要注意的是,fn()和fn2的書(shū)寫(xiě)順序,不管函數(shù)聲明順序是怎樣,只管函數(shù)的調(diào)用順序.上面的代碼是先調(diào)用fn()執(zhí)行1,2
再調(diào)用fn2(),這里先打印出4,再打印出3,這是因?yàn)閟etTimeout設(shè)置了1秒之后執(zhí)行
除了setTimeout可以模擬異步操作之外,回調(diào)函數(shù)也可以模擬異步,
function printAsync(str, callback) {
console.log(str);
if (callback) setTimeout(callback, 1000);
}
printAsync('aaa', function () {
printAsync('bbb', function () {
printAsync('ccc', function () {
printAsync('ddd');
})
})
});
//aaa
//bbb
//ccc
//ddd
看到這么多的})是不是有點(diǎn)煩躁诺祸?這還是相對(duì)簡(jiǎn)單的,沒(méi)什么復(fù)雜的業(yè)務(wù)邏輯,像這種多個(gè)回調(diào)函數(shù)的多層嵌套會(huì)讓代碼的可讀性直線(xiàn)下降,因此又稱(chēng)為回調(diào)地獄祭芦。
Promise
Promise是一種處理異步的方式,解決了函數(shù)的異步回調(diào)問(wèn)題,在通常情況下,如果使用多級(jí)回調(diào)函數(shù),代碼很難維護(hù),這樣就衍生了一種新的方式更好的處理這個(gè)問(wèn)題.Promisse還有個(gè)好處就是捕捉錯(cuò)誤信息.
Promise對(duì)象有以下兩個(gè)特點(diǎn)筷笨。
(1)對(duì)象的狀態(tài)不受外界影響。Promise對(duì)象代表一個(gè)異步操作龟劲,有三種狀態(tài):pending(進(jìn)行中)胃夏、fulfilled(已成功)和rejected(已失敗)昌跌。只有異步操作的結(jié)果仰禀,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無(wú)法改變這個(gè)狀態(tài)蚕愤。這也是Promise這個(gè)名字的由來(lái)答恶,它的英語(yǔ)意思就是“承諾”,表示其他手段無(wú)法改變审胸。
(2)一旦狀態(tài)改變亥宿,就不會(huì)再變卸勺,任何時(shí)候都可以得到這個(gè)結(jié)果砂沛。Promise對(duì)象的狀態(tài)改變,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected曙求。只要這兩種情況發(fā)生碍庵,狀態(tài)就凝固了,不會(huì)再變了悟狱,會(huì)一直保持這個(gè)結(jié)果静浴,這時(shí)就稱(chēng)為 resolved(已定型)。如果改變已經(jīng)發(fā)生了挤渐,你再對(duì)Promise對(duì)象添加回調(diào)函數(shù)苹享,也會(huì)立即得到這個(gè)結(jié)果。這與事件(Event)完全不同浴麻,事件的特點(diǎn)是得问,如果你錯(cuò)過(guò)了它,再去監(jiān)聽(tīng)软免,是得不到結(jié)果的宫纬。
我們通過(guò) 一個(gè)例子來(lái)看看Promise的具體用法
let ajax = function() {
console.log('執(zhí)行');
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve()
},1000)
})
}
ajax().then(()=>{
console.log('promise'+':'+'timeout1');
})
//執(zhí)行
//promise timeout1
then方法可以接受兩個(gè)回調(diào)函數(shù)作為參數(shù)。第一個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)閞esolved時(shí)調(diào)用膏萧,第二個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)閞ejected時(shí)調(diào)用漓骚。其中蝌衔,第二個(gè)函數(shù)是可選的,不一定要提供蝌蹂。這兩個(gè)函數(shù)都接受Promise對(duì)象傳出的值作為參數(shù)噩斟。
Promise還可以通過(guò)catch來(lái)捕獲錯(cuò)誤,我們來(lái)看下代碼的實(shí)現(xiàn)
let ajax = function(num){
console.log('開(kāi)始執(zhí)行');
return new Promise(function(resolve,reject){
if(num > 5){
resolve()
}else{
throw new Error('出錯(cuò)了')
}
})
}
ajax(1).then(()=>{
console.log('log',6);
}).catch((err)=>{
console.log('catch',err);
})
ajax(6).then(()=>{
console.log('log',6);
}).catch((err)=>{
console.log('catch',err);
})
如果出現(xiàn)錯(cuò)誤,可以得知哪一步出錯(cuò),可以幫助我們解決bug
除了這些還有一些高級(jí)用法,
function loadImg(src){
return new Promise((resolve,reject)=>{
let img = document.createElement('img')
img.src = src
img.onload = function(){
resolve(img)
}
img.onerror = function(err){
reject(err)
}
})
}
function showImgs(imgs){
imgs.forEach(function(img){
document.body.appendChild(img)
})
}
Promise.all([
loadImg('https://cdn.pixabay.com/photo/2018/06/10/00/11/seagull-3465550__340.jpg'),
loadImg('https://cdn.pixabay.com/photo/2018/06/10/21/29/white-currant-3467373__340.jpg'),
loadImg('https://cdn.pixabay.com/photo/2018/06/12/03/41/butterbur-3469942__340.jpg'),
]).then(showImgs)
通過(guò)Promise.all(),可以等到三張圖片數(shù)據(jù)全部加載完成之后再渲染到頁(yè)面上
當(dāng)然還有一種高級(jí)用法
function loadImg(src){
return new Promise((resolve,reject)=>{
let img = document.createElement('img')
img.src = src
img.onload = function(){
resolve(img)
}
img.onerror = function(err){
reject(err)
}
})
}
function showImgs(img){
document.body.appendChild(img)
}
Promise.race([
loadImg('https://cdn.pixabay.com/photo/2018/06/10/00/11/seagull-3465550__340.jpg'),
loadImg('https://cdn.pixabay.com/photo/2018/06/10/21/29/white-currant-3467373__340.jpg'),
loadImg('https://cdn.pixabay.com/photo/2018/06/12/03/41/butterbur-3469942__340.jpg'),
]).then(showImgs)
Promise.race的用法就是孤个,只要Promise狀態(tài)改變亩冬,有數(shù)據(jù)到來(lái),我就把第一條到來(lái)的數(shù)據(jù)渲染到頁(yè)面上硼身,其他的不再響應(yīng)硅急。