開(kāi)始之前
JavaScript是單線程語(yǔ)言,所以都是同步執(zhí)行的,要實(shí)現(xiàn)異步就得通過(guò)回調(diào)函數(shù)的方式,但是過(guò)多的回調(diào)會(huì)導(dǎo)致回調(diào)地獄,代碼既不美觀,也不易維護(hù),所以就有了promise;Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大。它由社區(qū)最早提出和實(shí)現(xiàn),ES6 將其寫進(jìn)了語(yǔ)言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了 Promise 對(duì)象。Promise 是一個(gè)擁有 then 方法的對(duì)象或函數(shù)。

問(wèn)題探討
下面通過(guò)多個(gè)示例來(lái)感受一下不使用 promise 時(shí),處理相應(yīng)問(wèn)題的不易,及生成了不便閱讀的代碼。
定時(shí)嵌套
假設(shè)我們想要實(shí)現(xiàn)網(wǎng)頁(yè)上一個(gè)盒子先向右邊移動(dòng),移動(dòng)到一定距離后逐漸縮小,縮小到一定大小后停止縮小。下面我們用兩個(gè)定時(shí)器去實(shí)現(xiàn),一個(gè)定時(shí)器控制移動(dòng),當(dāng)該定時(shí)器結(jié)束后啟動(dòng)另一個(gè)定時(shí)器控制縮小。
<!DOCTYPE html>
<html>
<head>
<title>JavaScript</title>
<style type="text/css">
#box{
height: 100px;
width: 100px;
background-color: #3498db;
position: absolute;
}
</style>
</head>
<body>
<div id="box"></div>
<script type="text/javascript">
function interval(callback,delay=100){
let id = setInterval(()=>callback(id),delay)
}
const box = document.getElementById('box');
interval((timeId)=>{
const left = Number.parseInt(window.getComputedStyle(box).left);
box.style.left = left + 10 + 'px';
if(left > 200){
clearInterval(timeId);
interval((timeId)=>{
const width = Number.parseInt(window.getComputedStyle(box).width);
const height = Number.parseInt(window.getComputedStyle(box).height);
box.style.width = width - 5 + 'px';
box.style.height = height - 5 + 'px';
if(width <= 50){
clearInterval(timeId)
}
})
}
});
</script>
</body>
</html>
圖片加載
圖片加載完成后設(shè)置一個(gè)圖片邊框
<!DOCTYPE html>
<html>
<head>
<title>JavaScript</title>
</head>
<body>
<script type="text/javascript">
function loadImg(file,resolve,reject){
const img = new Image();
img.src = file;
img.onload = () => {
resolve(img);
};
img.onerror = () => {
reject(new Error('load fail'))
}
document.body.appendChild(img);
}
loadImg('https://static.collectui.com/shots/4418237/health-landing-page-large',
image => {
console.log('圖片加載成功,開(kāi)始修改邊框');
image.style.border = "5px solid #3498db";
},
error => {
console.log(error);
}
)
</script>
</body>
</html>
疫情解除,員工復(fù)工通告
<!DOCTYPE html>
<html>
<head>
<title>JavaScript</title>
</head>
<body>
<script type="text/javascript">
function notice(msg,then){
then(msg);
}
function work(){
notice('全國(guó)上下正在積極應(yīng)對(duì)疫情變化', msg=>{
console.log(msg);
notice('疫情已經(jīng)結(jié)束,請(qǐng)各級(jí)政府通知企業(yè)復(fù)工', msg => {
console.log(`收到黨中央,國(guó)務(wù)院消息:${msg}`);
setTimeout(()=>{
notice('請(qǐng)各企業(yè)有序復(fù)工', msg => {
console.log(`收到政府消息:${msg}`);
notice('請(qǐng)員工在規(guī)定時(shí)間內(nèi)到到崗上班,但仍需注意防護(hù)', msg => {
console.log(`收到企業(yè)消息:${msg}`);
setTimeout(()=>{
notice('員工已到崗',msg => {
console.log(`收到員工消息:${msg},通知結(jié)束`);
})
},1000)
})
})
},1000)
})
})
}
work();
</script>
</body>
</html>
上面的實(shí)例可以看出在沒(méi)有使用Promise的時(shí)候,一些相互關(guān)聯(lián)的異步操作就會(huì)一層層的嵌套,形成了回調(diào)地獄,而且對(duì)于閱讀和維護(hù)代碼帶來(lái)了極大的不便。
Promise 基本語(yǔ)法
Promise 初體驗(yàn)
在開(kāi)始講解基本語(yǔ)法之前先改一下疫情復(fù)工通知的例子,感受一下promise帶來(lái)的便利。
<!DOCTYPE html>
<html>
<head>
<title>JavaScript</title>
</head>
<body>
<script type="text/javascript">
let country = new Promise((resolve,reject)=>{
console.log('全國(guó)上下正在積極應(yīng)對(duì)疫情變化');
resolve('疫情已經(jīng)結(jié)束,請(qǐng)各級(jí)政府通知企業(yè)復(fù)工');
})
let goverment = country.then(msg=>{
console.log(`收到黨中央,國(guó)務(wù)院消息:${msg}`);
return {
then(resolve){
setTimeout(()=>{
resolve('請(qǐng)各企業(yè)有序復(fù)工');
},1000);
}
}
})
let enterment = goverment.then(msg=>{
console.log(`收到政府消息:${msg}`);
return{
then(resolve){
setTimeout(()=>{
resolve('請(qǐng)員工在規(guī)定時(shí)間內(nèi)到崗上班,但仍需注意防護(hù)')
},1000);
}
}
})
let employee = enterment.then(msg=>{
console.log(`收到企業(yè)消息:${msg}`);
return{
then(resolve){
setTimeout(()=>{
resolve('員工已到崗');
},1000)
}
}
})
employee.then(msg=>{
console.log(`收到員工消息:${msg},通知結(jié)束`);
})
</script>
</body>
</html>
使用promise后,保證實(shí)現(xiàn)了原有功能,寫法上各級(jí)專注負(fù)責(zé)自己所管轄的內(nèi)容,避免了代碼的相互嵌套,使代碼變得更加容易閱讀,維護(hù)起來(lái)也更加方便。
理解Promise
promise可以按照字面意思理解——承諾。那這次疫情來(lái)講。國(guó)家政府通知全民可以進(jìn)行正常的工作生活就是對(duì)人民的一種承諾。如果疫情控制住了,結(jié)束了就是成功,我們就可以不用在家辦公或者隔離了。如果疫情沒(méi)有徹底控制住,就是拒絕,我們?nèi)院美^續(xù)現(xiàn)在的生活工作狀態(tài)。
- 一個(gè)
promise必須有一個(gè)then的方法用于處理狀態(tài)和改變。
狀態(tài)說(shuō)明:
Promise包含pendingfulfilledreject三種狀態(tài) -
pending指初始等待狀態(tài),初始化promise的狀態(tài) -
resolve指已經(jīng)解決,將promise狀態(tài)設(shè)置為fulfilled -
reject指拒絕處理,將promise狀態(tài)設(shè)置為rejected -
promise是生產(chǎn)者,通過(guò)resolve與reject函數(shù)告知結(jié)果 -
promise非常適合需要一定執(zhí)行時(shí)間的異步任務(wù)(一般網(wǎng)絡(luò)請(qǐng)求都是異步任務(wù)) - 狀態(tài)量一旦改變就不可改改變
promise是一種隊(duì)列狀態(tài),就像多米多骨牌,狀態(tài)由上個(gè)傳遞到下一個(gè),然后一個(gè)個(gè)往下傳,當(dāng)然其中任何一個(gè)promise也是可以改變狀態(tài)的。
圖片2
promise在沒(méi)有使用resolve和reject更改狀態(tài)時(shí),狀態(tài)為pending
console.log(
new Promise((resolve,reject)=>{});
) // Promise(<pending>)
promise使用ressolve或reject改變狀態(tài)后
console.log(
new Promise((resolve, reject) => {
resolve("fulfilled");
})
); //Promise {<resolved>: "fulfilled"}
console.log(
new Promise((resolve, reject) => {
reject("rejected");
})
); //Promise {<rejected>: "rejected"}
promise創(chuàng)建任務(wù)時(shí)會(huì)立即執(zhí)行同步任務(wù),then會(huì)放在異步微任務(wù)中執(zhí)行,需要等待同步任務(wù)執(zhí)行后才執(zhí)行。
let promise = new Promise((resolve,reject)=>{
resolve('fulfilled');
console.log('第一次打印');
});
promise.then(msg=>{
console.log(msg);
});
console.log('第二次打印')

上面的結(jié)果可以看出,
promise狀態(tài)改變后產(chǎn)生的微任務(wù)會(huì)在所有宏任務(wù)執(zhí)行后在執(zhí)行。
promise操作都是在其他代碼后執(zhí)行,下面會(huì)先輸出第一次輸出,之后才會(huì)彈出success
-
promise的thencatchfinally的方法都是異步任務(wù) - 程序需要將主任務(wù)執(zhí)行完才會(huì)執(zhí)行一步隊(duì)列任務(wù)
let promise = new Promise((resolve,reject)=>{resolve('success')})
promise.then(alert);
alert('第一次輸出');
promise.then(()=>{
alert('success之后彈出')
})
下面這個(gè)例子將在3秒后將promise狀態(tài)設(shè)置為fulfilled,然后執(zhí)行then方法
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('fulfilled');
},3000)
}).then(
(msg)=>{
console.log(msg);
},
(error)=>{
console.log(error);
}
)
狀態(tài)改變了就不能再修改了,下面先通過(guò)resolve改變?yōu)槌晒顟B(tài),表示promise狀態(tài)已經(jīng)完成,就不能用使用reject更改狀態(tài)了
new Promise((resolve, reject) => {
resolve("操作成功");
reject(new Error("請(qǐng)求失敗"));
}).then(
msg => {
console.log(msg);
},
error => {
console.log(error);
}
);
下面的例子中p2返回了p1的狀態(tài)已經(jīng)無(wú)意義了,后面then時(shí)對(duì)p1狀態(tài)的處理。即如果resolve參數(shù)是一個(gè)promise將會(huì)改變他的狀態(tài)。
let p1 = new Promise((resolve,reject)=>{
resolve('fulfilled');
// reject('reject');
});
let p2 = new Promise((resolve,reject)=>{
resolve(p1);
}).then(
value=>{
console.log(value);
},
reason=>{
console.log(reason);
}
);
當(dāng)promise作為參數(shù)傳遞時(shí),需要等待promise執(zhí)行完才可以繼承,下面的p2需要等待p1執(zhí)行完成。
- 因?yàn)?code>p2 的
resolve返回了p1的promise,所以此時(shí)p2的then方法已經(jīng)時(shí)p1的狀態(tài)了 - 正因?yàn)橐陨显?code>then的第一個(gè)函數(shù)輸出了
p1的resolve的參數(shù)
let p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('操作成功');
},2000)
})
let p2 = new Promise((resolve,reject)=>{
resolve(p1);
})
p2.then(msg=>{
console.log(msg);
}).catch(error=>{
console.log(error)
})
then的使用
一個(gè)promise需要提供一個(gè)then方法來(lái)訪問(wèn)promise的結(jié)果,then相當(dāng)于定義當(dāng)promise狀態(tài)發(fā)生改變的處理,即promise處理異步操作,then用來(lái)處理結(jié)果。
promise就像疫情中的真政府,then就是民眾,如果疫情被控制住了就分fulfilled, 如果沒(méi)有抑制,就reject。那么民眾(then)就要處理不同的狀態(tài)。
then方法必須返回promise,用戶返回或者系統(tǒng)自動(dòng)返回。第一個(gè)函數(shù)在
resolve狀態(tài)時(shí)執(zhí)行,即執(zhí)行resolve時(shí)執(zhí)行then的第一個(gè)函數(shù)處理成功的狀態(tài)第二個(gè)函數(shù)在
reject狀態(tài)時(shí)執(zhí)行,即執(zhí)行reject時(shí)執(zhí)行then的第二個(gè)函數(shù)處理失敗狀態(tài),該函數(shù)是可選的。兩個(gè)函數(shù)都接收
promise傳出的值做為參數(shù)也可以使用
catch來(lái)處理失敗的狀態(tài)(上面的最后一個(gè)例子)-
如果
then返回promise,下一個(gè)then會(huì)在當(dāng)前promise狀態(tài)改變后繼續(xù)執(zhí)行語(yǔ)法說(shuō)明
then的語(yǔ)法如下,onFulfilled函數(shù)處理fulfilled狀態(tài)。onReject函數(shù)處理rejected狀態(tài)
- onFulfilled 或 onRejected 不是函數(shù)將被忽略
- 兩個(gè)函數(shù)只會(huì)被調(diào)用一次
- onFulfilled在promise執(zhí)行成功時(shí)調(diào)用
- onRejected在promise執(zhí)行拒絕時(shí)調(diào)用
promise.then(onFulfilled, onRejected)
基礎(chǔ)知識(shí)
then會(huì)在promise執(zhí)行完成后執(zhí)行,then第一個(gè)函數(shù)在resolve成功狀態(tài)執(zhí)行
let promise = new Promise((resolve, reject) => {
resolve("success");
}).then(
value => {
console.log(`解決:${value}`);
},
reason => {
console.log(`拒絕:${reason}`);
}
);
then中第二個(gè)參數(shù)在失敗狀態(tài)執(zhí)行
let promise = new Promise((resolve, reject) => {
reject("is error");
});
promise.then(
msg => {
console.log(`成功:${msg}`);
},
error => {
console.log(`失敗:${error}`);
}
);
如果只關(guān)心失敗時(shí)狀態(tài),then的第一個(gè)參數(shù)傳遞null
let promise = new Promise((resolve, reject) => {
reject("is error");
});
promise.then(null, error => {
console.log(`失敗:${error}`);
});
promise傳向then的值,如果then沒(méi)有可處理的函數(shù),會(huì)一直向后傳遞(現(xiàn)實(shí)中一般不會(huì)這么寫)
let p1 = new Promise((resolve, reject) => {
reject("rejected");
})
.then()
.then(
null,
f => console.log(f)
);
下面這里例子 如果 onFulfilled不是函數(shù)且promise執(zhí)行成功,p2執(zhí)行成功并返回相同值
let promise = new Promise((resolve, reject) => {
resolve("resolve");
});
let p2 = promise.then();
p2.then().then(resolve => {
console.log(resolve);
});
如果 onRejected 不是函數(shù)且promise拒絕執(zhí)行,p2 拒絕執(zhí)行并返回相同值
let promise = new Promise((resolve, reject) => {
reject("reject");
});
let p2 = promise.then(() => {});
p2.then(null, null).then(null, reject => {
console.log(reject);
});
鏈?zhǔn)秸{(diào)用
每次then都是一個(gè)全新的promise,默認(rèn)then返回的狀態(tài)是fulfilled
let promise = new Promise((resolve,reject)=>{
resolve('fulfilled');
}).then(resolve=>{
console.log(resolve);
}).then(resolve=>{
console.log(resolve);
})

每次的
then都是一個(gè)全新的promise,不要以為上一個(gè)promise狀態(tài)會(huì)影響以后then返回的狀態(tài)
let p1 = new Promise(resolve=>{
resolve();
});
let p2 = p1.then(()=>{
console.log('這是對(duì)p1進(jìn)行處理')
});
p2.then(()=>{
console.log('這是對(duì)p2進(jìn)行處理')
});
console.log(p1); // Promise {<resolve>}
console.log(p2); // Promise {<pending>}
# 再試試把上面兩行放在 setTimeout里
setTimeout(() => {
console.log(p1); // Promise {<resolved>}
console.log(p2); // Promise {<resolved>}
});
下面這個(gè)例子 then 是對(duì)上個(gè)promise 的rejected 的處理,每個(gè) then 會(huì)是一個(gè)新的promise,默認(rèn)傳遞 fulfilled狀態(tài)
new Promise((resolve, reject) => {
reject();
})
.then(
resolve => console.log("fulfilled"),
reject => console.log("rejected")
)
.then(
resolve => console.log("fulfilled"),
reject => console.log("rejected")
)
.then(
resolve => console.log("fulfilled"),
reject => console.log("rejected")
);
/*執(zhí)行結(jié)果*/
rejected
fulfilled
fulfilled
在看下面一個(gè)例子
new Promise((resolve,reject)=>{
reject('error')
}).then(res=>console.log(res),error=>{console.log(error);})
.then(res=>console.log(res),err=>console.log(err))
/*執(zhí)行結(jié)果*/
error
undifine
在看一個(gè)例子
new Promise((resolve,reject)=>{
reject('error')
}).then(res=>console.log(res),error=>{console.log(error);return 123})
.then(res=>console.log(res),err=>console.log(err))
/*執(zhí)行結(jié)果*/
error
undifine
在上面我已經(jīng)介紹過(guò)在 promise實(shí)例中,如果狀態(tài)改變?yōu)閒ulfilled,則可以返回resolve函數(shù),然后做為參數(shù)傳到then中成功處理函數(shù)中,如果狀態(tài)為rejected,則可以傳到reject函數(shù),然后作為參數(shù)傳遞到then中的拒絕函數(shù)中處理。而在then方法中是沒(méi)有JS部署好的resolve和reject函數(shù)的。再往上的第四個(gè)例子紅可以看到P2的狀態(tài)是pending的。但是我們可以字then方法中通過(guò)設(shè)置返回值,創(chuàng)建新的Promise對(duì)象之后將返回值傳遞到下一個(gè)then方法中的成功或拒絕函數(shù)中去,如果沒(méi)有返回值則默認(rèn)為undefined。就是上面兩個(gè)例子所要表述的內(nèi)容。
如果內(nèi)部返回promise時(shí),將使用該promise
let p1 = new Promise(resolve=>{
resolve();
});
let p2 = p1.then(()=>{
return new Promise((resolve,reject)=>{
resolve('內(nèi)部中的Promise');
})
});
p2.then(res=>{
console.log(res);
}); // 內(nèi)部中的Promise
如果 then 返回promise 時(shí),后面的then 就是對(duì)返回的 promise 的處理,需要等待該 promise 變更狀態(tài)后執(zhí)行。
let promise = new Promise(resolve => resolve());
let p1 = promise.then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`p1`);
resolve();
}, 2000);
});
}).then(() => {
return new Promise((a, b) => {
console.log(`p2`);
});
});
Thenables
包含 then 方法的對(duì)象就是一個(gè) promise ,系統(tǒng)將傳遞 resolvePromise 與 rejectPromise 做為函數(shù)參數(shù)。
文章開(kāi)頭的地方使用Promise復(fù)寫的復(fù)工通知就是用的該方法。
當(dāng)然類也可以
new Promise((resolve, reject) => {
resolve(
class {
static then(resolve, reject) {
setTimeout(() => {
resolve("解決狀態(tài)");
}, 2000);
}
}
);
}).then(
res => {
console.log(`fulfilled: ${res}`);
},
err => {
console.log(`rejected: ${err}`);
}
);
如果對(duì)象中的 then 不是函數(shù),則將對(duì)象做為值傳遞
new Promise((resolve, reject) => {
resolve();
})
.then(() => {
return {
then: "后盾人"
};
})
.then(v => {
console.log(v);
});
利用Promise用原聲JS封裝AJAX
function request(url){
return new Promise((resolve,reject)=>{
let XHR = XMLHttpRequest ? new XMLHttpRequest(): new window.ActiveXObject('Microsoft.XMLHTTP');
XHR.onreadystatechange = funciton(){
if(XHR.readyState === 4){
if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
try{
let response = JSON.parse(XHR.responseText);
resolve(response);
}catch(e){
reject(e);
}
}
}else{
reject(new Error('"Request was unsuccessful: " + XHR.statusText'));
}
}
XHR.open('GET', url, true);
XHR.send(null);
})
}
catch
除了正常使用reject,Promise里面發(fā)生任何的異常,都會(huì)觸發(fā)失敗的狀態(tài)。
下面使用未定義的變量會(huì)觸發(fā)失敗狀態(tài)
let promise = new Promise((resolve, reject) => {
abc;
}).then(
value => console.log(value),
reason => console.log(reason)
);
如果onFulfilled或onReject拋出異常,則p2拒絕執(zhí)行并返回拒絕原因
let promise = new Promise((resolve,reject)=>{
throw new Error('fail');
});
let p2 = promise.then();
p2.then().then(null,reject=>{
console.log(reject);
})
catch用于失敗狀態(tài)的處理函數(shù),等同于 then(null,reject){}
- 建議使用
catch處理錯(cuò)誤 - 將
catch放在最后面用于統(tǒng)一處理前面發(fā)生的錯(cuò)誤
const promise = new Promise((resolve, reject) => {
reject(new Error("Notice: Promise Exception"));
}).catch(msg => {
console.error(msg);
});
catch可以捕獲之前所有的promise的錯(cuò)誤,所以將catch放在最后。下面中例子中catch捕獲了第一個(gè)個(gè)then返回的promise的錯(cuò)誤。
new Promise((resolve, reject) => {
resolve();
})
.then(() => {
return new Promise((resolve, reject) => {
reject(".then ");
});
})
.then(() => {})
.catch(msg => {
console.log(msg);
});
錯(cuò)誤是冒泡的操作的,下面沒(méi)有任何一個(gè)then 定義第二個(gè)函數(shù),將一直冒泡到 catch 處理錯(cuò)誤
new Promise((resolve,reject)=>{
reject(new Error('請(qǐng)求失敗'));
})
.then(msg=>{})
.then(msg=>{})
.catch(error=>{
console.log(error)
})
catch 也可以捕獲對(duì) then 拋出的錯(cuò)誤處理
new Promise((resolve, reject) => {
resolve();
})
.then(msg => {
throw new Error("這是then 拋出的錯(cuò)誤");
})
.catch((error) => {
console.log(error);
});
建議將錯(cuò)誤交給catch處理,而不是在 then中完成。
? 處理機(jī)制
在promise中拋出的錯(cuò)誤也會(huì)在catch中捕獲,其實(shí)可以理解為在內(nèi)部自動(dòng)執(zhí)行了try...catch...
new Promise((resolve, reject) => {
try {
throw new Error("fail");
} catch (error) {
reject(error);
}
}).catch(msg => {
console.log(msg);
});
但像下面的在異步中 throw 將不會(huì)觸發(fā) catch,而使用系統(tǒng)錯(cuò)誤處理
new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error("fail");
}, 2000);
}).catch(msg => {
console.log(msg + "異步處理");
});
已經(jīng)處理過(guò)的將不會(huì)在catch重復(fù)處理
new Promise((resolve,reject)=>{
reject('error')
}).then(null,error=>{
console.log(error);
}).then()
.catch(error=>{
console.log(error)
})
在 catch 中發(fā)生的錯(cuò)誤也會(huì)拋給最近的錯(cuò)誤處理
new Promise((resolve, reject) => {
reject();
})
.catch(msg => {
throw new Error('error')
})
.then(null, error => {
console.log(error);
});
? 定制錯(cuò)誤
可以根據(jù)不同的錯(cuò)誤類型進(jìn)行定制操作,下面將參數(shù)錯(cuò)誤與404錯(cuò)誤分別進(jìn)行了處理
<script type="text/javascript">
class ParamError extends Error{
constructor(message){
super(message);
this.name = 'ParamError'
}
}
class HttpError extends Error{
constructor(message){
super(message);
this.name = 'HttpError'
}
}
function request(url){
return new Promise((resolve,reject)=>{
if(!/^http/.test(url)){
throw new ParamError('請(qǐng)求地址錯(cuò)誤');
}
let XHR = new XMLHttpRequest();
XHR.onreadystatechange = funciton(){
if(XHR.readyState === 4){
if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
try{
let response = JSON.parse(XHR.responseText);
resolve(response);
}catch(e){
reject(e);
}
}else if(XHR.statux === 404){
reject(new HttpError('網(wǎng)址不存在'));
}
}else{
reject(new Error('"Request was unsuccessful: " + XHR.statusText'));
}
}
XHR.open('GET',url,true);
XHR.send(null);
})
}
request('http://www.abc.com')
.then(response=>{
console.log(response)
})
.catch(error=>{
if(error instanceof ParamError){
console.log(error.message)
}
if(error instanceof HttpError){
alert(error.message);
}
console.log(error)
})
</script>
finally
無(wú)論狀態(tài)是resolve 或 reject 都會(huì)執(zhí)行此動(dòng)作,finally 與狀態(tài)無(wú)關(guān)。
const promise = new Promise((resolve, reject) => {
reject("hdcms");
})
.then(msg => {
console.log("resolve");
})
.catch(msg => {
console.log("reject");
})
.finally(() => {
console.log("resolve/reject狀態(tài)都會(huì)執(zhí)行");
});
下面使用 finally 處理加載狀態(tài),當(dāng)請(qǐng)求完成時(shí)移除加載圖標(biāo)。
request('http://localhost:8080')
.then(response=>{
console.log(response)
})
.catch(error=>{
console.log(error)
})
.finally(()=>{
this.loading = false;
})
實(shí)例操作
下面是封裝的timeout 函數(shù),使用定時(shí)器操作更加方便
function timeout(times) {
return new Promise(resolve => {
setTimeout(resolve, times);
});
}
timeout(3000)
.then(() => {
console.log("3秒后執(zhí)行");
return timeout(1000);
})
.then(() => {
console.log("執(zhí)行上一步的promise后1秒執(zhí)行");
});
封閉 setInterval 定時(shí)器并實(shí)現(xiàn)動(dòng)畫(huà)效果
<body>
<style>
div {
width: 100px;
height: 100px;
background: yellowgreen;
position: absolute;
}
</style>
<div></div>
</body>
<script>
function interval(delay = 1000, callback) {
return new Promise(resolve => {
let id = setInterval(() => {
callback(id, resolve);
}, delay);
});
}
interval(100, (id, resolve) => {
const div = document.querySelector("div");
let left = parseInt(window.getComputedStyle(div).left);
div.style.left = left + 10 + "px";
if (left >= 200) {
clearInterval(id);
resolve(div);
}
}).then(div => {
interval(50, (id, resolve) => {
let width = parseInt(window.getComputedStyle(div).width);
div.style.width = width - 10 + "px";
if (width <= 20) {
clearInterval(id);
}
});
});
</script>
接口擴(kuò)展
resolve
使用 promise.resolve 方法可以快速的返回一個(gè)promise對(duì)象
Promise.resolve('success').then(value=>{
console.log(value);
})
下面將請(qǐng)求結(jié)果緩存,如果再次請(qǐng)求時(shí)直接返回帶值的 promise
<script type="text/javascript">
const api = 'http://localhost:3000';
function request(url){
return new Promise((resolve,reject)=>{
if(!/^http/.test(url)){
throw new ParamError('請(qǐng)求地址錯(cuò)誤');
}
let XHR = new XMLHttpRequest();
XHR.onreadystatechange = function(){
if(XHR.readyState === 4){
if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
try{
let response = JSON.parse(XHR.responseText);
resolve(response);
}catch(e){
reject(e);
}
}else if(XHR.status === 404){
reject(new HttpError('網(wǎng)址不存在'));
}else{
reject(new Error("Request was unsuccessful: " + XHR.statusText));
}
}
}
XHR.open('GET',url,true);
XHR.send(null);
})
}
</script>
<script type="text/javascript">
function queryAllNumbers(){
const cache = queryAllNumbers.cache || (queryAllNumbers.chche = new Map());
if(cache.has('numbers')){
console.log('走緩存了');
return Promise.resolve(cache.get('numbers'))
}
return request(`${api}/User/GetClassNumber`)
.then(response=>{
cache.set('numbers',response.Data);
console.log('沒(méi)走緩存');
return response.Data;
})
}
queryAllNumbers()
.then(response=>{
console.log(response)
})
</script>
reject
和 Promise.resolve 類似,reject 生成一個(gè)失敗的promise
Promise.reject("fail").catch(error => console.log(error));
下面使用 Project.reject 設(shè)置狀態(tài)
new Promise(resolve => {
resolve("ac=bc");
})
.then(v => {
if (v != "123") return Promise.reject(new Error("fail"));
})
.catch(error => {
console.log(error);
});
all
使用Promise.all 方法可以同時(shí)執(zhí)行多個(gè)并行異步操作,比如頁(yè)面加載時(shí)同進(jìn)獲取課程列表與推薦課程。
- 任何一個(gè)
Promise執(zhí)行失敗就會(huì)調(diào)用catch方法 - 適用于一次發(fā)送多個(gè)異步操作
- 參數(shù)必須是可迭代類型,如Array/Set
- 成功后返回
promise結(jié)果的有序數(shù)組
下例中當(dāng) p1、p2 兩個(gè) Promise 狀態(tài)都為 fulfilled時(shí),ps狀態(tài)才為fulfilled。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("第一個(gè)Promise");
}, 1000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("第二個(gè)Promise");
}, 1000);
});
let ps = Promise.all([p1, p2])
.then(results => {
console.log(results);
})
.catch(msg => {
console.log(msg);
});
根據(jù)用戶名獲取用戶,有任何一人用戶獲取不到時(shí) promise.all 狀態(tài)失敗,執(zhí)行 catch 方法
<script type="text/javascript">
function request(url){
return new Promise((resolve,reject)=>{
if(!/^http/.test(url)){
throw new ParamError('請(qǐng)求地址錯(cuò)誤');
}
let XHR = new XMLHttpRequest();
XHR.onreadystatechange = function(){
if(XHR.readyState === 4){
if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
try{
let response = JSON.parse(XHR.responseText);
resolve(response);
}catch(e){
reject(e);
}
}else if(XHR.status === 404){
reject(new HttpError('網(wǎng)址不存在'));
}else{
reject(new Error("Request was unsuccessful: " + XHR.statusText));
}
}
}
XHR.open('GET',url,true);
XHR.send(null);
})
}
const api = 'http://localhost:3000';
const promises = ['zhangsan','lisi'].map(name=>{
return request(`${api}/Course/GetCourseInfoById?name=${name}`)
})
Promise.all(promises)
.then(response=>{
console.log(response);
})
.catch(error=>{
console.log('Error:' + error);
})
</script>

可以將其他非
promise 數(shù)據(jù)添加到 all 中,它將被處理成 Promise.resolve
const promises = [
ajax(`${api}/user?name=zhangsan`),
ajax(`${api}/user?name=lisi`),
{ id: 3, name: "wangwu", email: "wangwu@qq.com" }
];
如果某一個(gè)promise沒(méi)有catch 處理,將使用promise.all 的catch處理
let p1 = new Promise((resolve, reject) => {
resolve("fulfilled");
});
let p2 = new Promise((resolve, reject) => {
reject("rejected");
});
Promise.all([p1, p2]).catch(reason => {
console.log(reason);
});
allSettled
allSettled 用于處理多個(gè)promise ,只關(guān)注執(zhí)行完成,不關(guān)注是否全部執(zhí)行成功,allSettled 狀態(tài)只會(huì)是fulfilled。
下面的p2 返回狀態(tài)為 rejected ,但promise.allSettled 不關(guān)心,它始終將狀態(tài)設(shè)置為 fulfilled 。
let p1 = new Promise((resolve, reject) => {
resolve("resolved");
});
let p2 = new Promise((resolve, reject) => {
reject("rejected");
});
Promise.allSettled([p1, p2])
.then(msg => {
console.log(msg);
})
下面是獲取用戶信息,但不關(guān)注某個(gè)用戶是否獲取不成功
let promises = [
ajax(`${api}/User/GetCourseInfoById?name=zhangsan`),
ajax(`${api}/User/GetCourseInfoById?name=lisi`)
];
Promise.allSettled(promises).then(response => {
console.log(response);
});
race
使用Promise.race() 處理容錯(cuò)異步,和race單詞一樣哪個(gè)Promise快用哪個(gè),哪個(gè)先返回用哪個(gè)。
- 以最快返回的promise為準(zhǔn)
- 如果最快返加的狀態(tài)為
rejected那整個(gè)promise為rejected執(zhí)行catch - 如果參數(shù)不是promise,內(nèi)部將自動(dòng)轉(zhuǎn)為promise
下面將第一次請(qǐng)求的異步時(shí)間調(diào)整為兩秒,這時(shí)第二個(gè)先返回就用第二人。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("第一個(gè)Promise");
}, 2000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("第二個(gè)Promise");
}, 1000);
});
Promise.race([p1, p2])
.then(results => {
console.log(results);
})
.catch(msg => {
console.log(msg);
});
獲取用戶信息,如果兩秒內(nèi)沒(méi)有結(jié)果 promise.race 狀態(tài)失敗,執(zhí)行catch 方法
let promises = [
ajax(`${api}/User/GetCourseInfoById?name=zhangsan`),
new Promise((a, b) =>
setTimeout(() => b(new Error("request fail")), 2000)
)
];
Promise.race(promises)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
任務(wù)隊(duì)列
如果 then 返回promise 時(shí),后面的then 就是對(duì)返回的 promise 的處理
下面使用 forEach 構(gòu)建的隊(duì)列,有以下幾點(diǎn)需要說(shuō)明
-
then內(nèi)部返回的promise更改外部的promise變量 - 為了讓任務(wù)繼承,執(zhí)行完任務(wù)需要將
promise狀態(tài)修改為fulfilled
function queue(nums) {
let promise = Promise.resolve();
nums.map(n => {
promise = promise.then(v => {
return new Promise(resolve => {
console.log(n);
resolve();
});
});
});
}
queue([1, 2, 3, 4, 5]);
下面再來(lái)通過(guò) reduce 來(lái)實(shí)現(xiàn)隊(duì)列
function queue(nums) {
return nums.reduce((promise, n) => {
return promise.then(() => {
return new Promise(resolve => {
console.log(n);
resolve();
});
});
}, Promise.resolve());
}
queue([1, 2, 3, 4, 5]);
async/await 語(yǔ)法糖
使用 async/await 是promise 的語(yǔ)法糖,可以讓編寫 promise 更清晰易懂,也是推薦編寫promise 的方式。
-
async/await本質(zhì)還是promise,只是更簡(jiǎn)潔的語(yǔ)法糖書(shū)寫 -
async/await使用更清晰的promise來(lái)替換 promise.then/catch 的方式
async
下面在 foo 函數(shù)前加上async,函數(shù)將返回promise,我們就可以像使用標(biāo)準(zhǔn)Promise一樣使用了。
下面在 foo 函數(shù)前加上async,函數(shù)將返回promise,我們就可以像使用標(biāo)準(zhǔn)Promise一樣使用了。
async function foo() {
return "foo foo foo";
}
console.log(hd());
foo().then(value => {
console.log(value);
});
如果有多個(gè)await 需要排隊(duì)執(zhí)行完成,我們可以很方便的處理多個(gè)異步隊(duì)列
async function foo(message) {
return new Promise(resolve => {
setTimeout(() => {
resolve(message);
}, 2000);
});
}
async function run() {
let h1 = await foo("第一次輸出");
console.log(h1);
let h2 = await foo("第二次輸出");
console.log(h2);
}
run();
await
使用 await 關(guān)鍵詞后會(huì)等待promise 完成
-
await后面一般是promise,如果不是直接返回 -
await必須放在 async 定義的函數(shù)中使用 -
await用于替代then使編碼更優(yōu)雅
下例會(huì)在 await 這行暫停執(zhí)行,直到等待 promise 返回結(jié)果后才繼執(zhí)行。
async function foo(message){
let p = new Promise(resolve=>{
setTimeout(()=>{
resolve(message)
},2000)
})
let result = await p;
console.log(result);
}
foo('輸出輸出');
一般await后面是外部其它的promise對(duì)象
async function foo(message) {
return new Promise(resolve => {
setTimeout(() => {
resolve(message);
}, 2000);
});
}
async function run() {
let h1 = await foo("第一次輸出");
console.log(h1);
let h2 = await foo("第二次輸出");
console.log(h2);
}
run();
? 錯(cuò)誤處理
async 內(nèi)部發(fā)生的錯(cuò)誤,會(huì)將必變promise對(duì)象為rejected 狀態(tài),所以可以使用catch 來(lái)處理
async function foo(){
console.log(bar);
}
foo().catch(error=>{
console.log(error);
})
多個(gè) await 時(shí)當(dāng)前面的出現(xiàn)失敗,后面的將不可以執(zhí)行
async function foo() {
await Promise.reject("fail");
await Promise.resolve("success").then(value => {
console.log(value);
});
}
foo();
如果對(duì)前一個(gè)錯(cuò)誤進(jìn)行了處理,后面的 await 可以繼續(xù)執(zhí)行
async function foo() {
await Promise.reject("fail").catch(e => console.log(e));
await Promise.resolve("success").then(value => {
console.log(value);
});
}
foo();
也可以使用 try...catch 特性忽略不必要的錯(cuò)誤
async function foo() {
try {
await Promise.reject("fail");
} catch (error) {}
await Promise.resolve("success").then(value => {
console.log(value);
});
}
foo();
? 并發(fā)執(zhí)行
有時(shí)需要多個(gè)await 同時(shí)執(zhí)行,有以下幾種方法處理,下面多個(gè)await 將產(chǎn)生等待
async function p1() {
return new Promise(resolve => {
setTimeout(() => {
console.log("houdunren");
resolve();
}, 2000);
});
}
async function p2() {
return new Promise(resolve => {
setTimeout(() => {
console.log("hdcms");
resolve();
}, 2000);
});
}
async function foo() {
await p1();
await p2();
}
foo();
使用 Promise.all() 處理多個(gè)promise并行執(zhí)行
async function foo() {
await Promise.all([p1(), p2()]);
}
foo();
