開始之前
JavaScript是單線程語(yǔ)言,所以都是同步執(zhí)行的刀森,要實(shí)現(xiàn)異步就得通過回調(diào)函數(shù)的方式,但是過多的回調(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ù)。
問題探討
下面通過多個(gè)示例來(lái)感受一下不使用 promise
時(shí)识窿,處理相應(yīng)問題的不易斩郎,及生成了不便閱讀的代碼。
定時(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('圖片加載成功,開始修改邊框');
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í)例可以看出在沒有使用Promise
的時(shí)候乃秀,一些相互關(guān)聯(lián)的異步操作就會(huì)一層層的嵌套,形成了回調(diào)地獄圆兵,而且對(duì)于閱讀和維護(hù)代碼帶來(lái)了極大的不便跺讯。
Promise 基本語(yǔ)法
Promise 初體驗(yàn)
在開始講解基本語(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é)束了就是成功系奉,我們就可以不用在家辦公或者隔離了。如果疫情沒有徹底控制住姑廉,就是拒絕缺亮,我們?nèi)院美^續(xù)現(xiàn)在的生活工作狀態(tài)。
- 一個(gè)
promise
必須有一個(gè)then
的方法用于處理狀態(tài)和改變庄蹋。
狀態(tài)說明:
Promise包含pending
fulfilled
reject
三種狀態(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)者,通過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)的扰柠。
promise在沒有使用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
的then
catch
finally
的方法都是異步任務(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)改變了就不能再修改了舔腾,下面先通過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)訪問promise
的結(jié)果氨距,then
相當(dāng)于定義當(dāng)promise
狀態(tài)發(fā)生改變的處理,即promise
處理異步操作徘钥,then
用來(lái)處理結(jié)果衔蹲。
promise就像疫情中的真政府,then就是民眾,如果疫情被控制住了就分fulfilled, 如果沒有抑制舆驶,就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ǔ)法說明
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
沒有可處理的函數(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)介紹過在 promise
實(shí)例中监署,如果狀態(tài)改變?yōu)閒ulfilled钠乏,則可以返回resolve函數(shù)晓避,然后做為參數(shù)傳到then中成功處理函數(shù)中,如果狀態(tài)為rejected暑塑,則可以傳到reject函數(shù)事格,然后作為參數(shù)傳遞到then
中的拒絕函數(shù)中處理驹愚。而在then方法中是沒有JS部署好的resolve和reject函數(shù)的逢捺。再往上的第四個(gè)例子紅可以看到P2的狀態(tài)是pending的劫瞳。但是我們可以字then方法中通過設(shè)置返回值志于,創(chuàng)建新的Promise對(duì)象之后將返回值傳遞到下一個(gè)then方法中的成功或拒絕函數(shù)中去恨憎,如果沒有返回值則默認(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ù)各聘。
文章開頭的地方使用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ò)誤是冒泡的操作的艾船,下面沒有任何一個(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)處理過的將不會(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)畫效果
<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('沒走緩存');
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
沒有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)沒有結(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)需要說明
-
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)通過 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ǔ)法糖書寫 -
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();