JavaScript被設(shè)計(jì)為單線程(webWoker可以處理多線程)虑鼎,利用事件輪詢機(jī)制辱匿,可以模擬出多線程效果,也就是異步操作炫彩,而回調(diào)函數(shù)callback是事件輪詢調(diào)用的目標(biāo)方法匾七。
但是,通過(guò)回調(diào)函數(shù)處理異步事件有很多不確定性江兢,并且容易陷入“回調(diào)地獄”-嵌套太深昨忆。于是,Promise概念被提出杉允,并且很多JavaScript框架(比如JQuery)支持的異步API都基于Promise理念構(gòu)建的邑贴。
1.什么是Promise?
Promise是一個(gè)對(duì)象叔磷,用來(lái)傳遞異步操作的信息拢驾,它代表了某個(gè)未來(lái)時(shí)刻才知道結(jié)果的事件,并且這個(gè)事件提供統(tǒng)一API接口世澜。
2. Promise對(duì)象
Promise原型對(duì)象提供的主要方法有:
//添加狀態(tài)改變時(shí)的回調(diào)函數(shù)
Promise.prototype.then(resolvedFunc, rejectedFunc)
//響應(yīng)rejected狀態(tài)的promise(如果前面有錯(cuò)誤拋出独旷,會(huì)產(chǎn)生一個(gè)rejected狀態(tài)的promise)
//相當(dāng)于promise.then(null, rejectedFunc)
Promise.prototype.catch(rejectedFunc)
參考一個(gè)標(biāo)準(zhǔn)的Promise實(shí)例:
var promise = new Promise(function(resolve, reject){
// 你的代碼---pending狀態(tài)
if (isSuccessful) {
resolve(value); // 成功
}
else {
reject(error); // 失敗
}
// 這里的代碼永遠(yuǎn)不會(huì)被運(yùn)行
});
promise
.then(
function(value){//resolved時(shí)調(diào)用},
function(error){// rejected時(shí)調(diào)用})
.catch(function(error){//有錯(cuò)誤拋出時(shí)調(diào)用});
promise對(duì)象有如下特點(diǎn):
- 可以利用promise對(duì)象創(chuàng)建一個(gè)異步操作。
- 有三種狀態(tài):pending, resolved和rejected寥裂。異步代碼運(yùn)行時(shí)為pending嵌洼,運(yùn)行后的結(jié)果只會(huì)是兩種:成功-resolved,或者失敗-rejected封恰。狀態(tài)變化是單行流動(dòng)麻养,不可逆轉(zhuǎn)。
- 在一個(gè)promise里诺舔,resolve或者reject方法只會(huì)被調(diào)用一次鳖昌。
- resolve()/reject()可以利用參數(shù)傳遞數(shù)據(jù)备畦,但是,只支持傳遞第一個(gè)參數(shù)许昨。也就是說(shuō)懂盐,promise決議只能傳遞單個(gè)值/對(duì)象。因此糕档,實(shí)際應(yīng)用中莉恼,需要將多個(gè)值封裝在一個(gè)對(duì)象中傳遞。
- then()和catch()函數(shù)都會(huì)默認(rèn)返回一個(gè)promise對(duì)象速那。
- 如果沒(méi)有給then()傳遞函數(shù)作為完成處理函數(shù)參數(shù)俐银,還是會(huì)有替代的默認(rèn)處理函數(shù),并且端仰,該默認(rèn)函數(shù)會(huì)把接受到的值傳遞給下一個(gè)promise對(duì)象捶惜。
getPromise(40, true).
then(null,null).then(function (value) {
console.log("resolved:"+value);
});
// 打印:resolved:40
// 可見(jiàn)荔烧,如果不設(shè)置then的處理函數(shù)參數(shù)吱七,resolved值40一直會(huì)被傳遞下去。
3. 一個(gè)Promise實(shí)例
下面是一個(gè)Promise例子茴晋,參考注釋陪捷。
創(chuàng)建promise的工廠方法:
var getPromise = function (val, isSuccessful) {
var promise = new Promise(function (resolve, reject) {
setTimeout(function () {
if (isSuccessful) {
// 決議成功,回調(diào)then()的第一個(gè)函數(shù)參數(shù)
resolve(val);
}
else {
// 決議失敗诺擅,回調(diào)then()的第二個(gè)函數(shù)參數(shù)
reject(new Error("oh, error!"));
}
}, 0);
});
return promise;
};
第一次測(cè)試(連續(xù)調(diào)用兩次promise):
getPromise(30, true).then((value) => {
console.log("the first resolved status: " + value);
// 返回一個(gè)promise對(duì)象市袖,該promise決議結(jié)果會(huì)決定下一個(gè)then()函數(shù)應(yīng)該調(diào)用哪個(gè)回調(diào)函數(shù)
// 如果不顯性返回promise對(duì)象,ES6會(huì)默認(rèn)創(chuàng)建一個(gè)空值promise對(duì)象最為返回值
return getPromise(20, true);
}, (error) => {
console.log("the second rejected status: " + error);
}).then((value) => {
console.log("the second resolved status: " + value);
// 一個(gè)rejected狀態(tài)的promise對(duì)象被返回
//由于后續(xù)沒(méi)有then()烁涌,因此catch函數(shù)捕獲錯(cuò)誤狀態(tài)
throw new Error('create a error!');
}, (error) => {
console.log("the second rejected status: " + error);
}).catch((error) => {
console.log("Catch: " + error);
});
//運(yùn)行結(jié)果:
the first resolved status: 30
the second resolved status: 20
Catch: Error: create a error!
第二次測(cè)試(多加一個(gè)then調(diào)用):
getPromise(30, true).then((value) => {
console.log("the first resolved status: " + value);
return getPromise(20, true);
}, (error) => {
console.log("the second rejected status: " + error);
}).then((value) => {
console.log("the second resolved status: " + value);
// 一個(gè)rejected狀態(tài)的promise對(duì)象被返回
//由于后續(xù)有then()苍碟,因此then函數(shù)的第二個(gè)回調(diào)函數(shù)被運(yùn)行
throw new Error('create a error!');
}, (error) => {
console.log("the second rejected status: " + error);
}).then((value) => {
console.log("the third resolved status: " + value);
}, (error) => {
console.log("the third rejected status: " + error);
}).catch((error) => {
console.log("Catch: " + error);
});
// 運(yùn)行結(jié)果:
the first resolved status: 30
the second resolved status: 20
the third rejected status: Error: create the first error!
第三次測(cè)試(修改getPromise函數(shù)):
var getPromise = function (val, isSuccessful) {
var promise = new Promise(function (resolve, reject) {
if (isSuccessful) {
resolve(val);
//resolve之后拋出錯(cuò)誤,是不會(huì)被捕獲的
throw new Error('error, error, error!');
}
else {
reject(new Error("oh, error!"));
}
});
return promise;
};
// 測(cè)試1和測(cè)試2的運(yùn)行結(jié)果不會(huì)改變
從上面的例子可以看到撮执,可以用同步書寫方式連續(xù)調(diào)用多個(gè)異步請(qǐng)求微峰。并且,Promise還提供了靜態(tài)函數(shù)幫助解決更復(fù)雜的異步編程場(chǎng)景抒钱。
4. Promise靜態(tài)方法
Promise提供的靜態(tài)方法有:
Promise.all([promise1, promise2 [,promiseN]])
Promise.race([promise1, promise2 [,promiseN])
Promise.resolve()
Promise.reject()
(1) Promise.all()
將多個(gè)promise實(shí)例包裝成一個(gè)新的promise對(duì)象蜓肆,只有多個(gè)promise的狀態(tài)都為resolved允瞧,Promise.all()的狀態(tài)才會(huì)變?yōu)閞esolved备韧。
繼續(xù)上面的例子志群,測(cè)試如下:
Promise.all([
getPromise(20, true),
getPromise(30, true),
getPromise(40, true)])
.then(function (value) {
console.log(value);
});
// 輸出為:[ 20, 30, 40 ]
每個(gè)Promise實(shí)例的resolved值都會(huì)暫存在一個(gè)數(shù)組里桨醋,最后,該數(shù)組被傳遞到Promise.all()的resolved回調(diào)函數(shù)崭庸。
(2) Promise.race()
將多個(gè)promise實(shí)例包裝成一個(gè)新的promise對(duì)象属划,只有第一個(gè)promise狀態(tài)為resolved時(shí)涧至,Promise.race()的狀態(tài)才變?yōu)閞esolved诅蝶。
并且退个,第一個(gè)promise的值會(huì)傳遞給Promise.race()募壕。
基于上面例子繼續(xù)測(cè)試:
Promise.race([
getPromise(20, true),
getPromise(30, false),
getPromise(40, false)])
.then(function (value) {
console.log(value);
}, function (error) {
console.log(error);
});
//輸出為: 20
Promise.race([
getPromise(20, false),
getPromise(30, true),
getPromise(40, false)])
.then(function (value) {
console.log(value);
}, function (error) {
console.log(error);
});
// 輸出為: [Error: oh, error!]
(3) Promise.resolve()
該方法會(huì)返回一個(gè)Promise對(duì)象,情況分為兩種:
- 如果目標(biāo)對(duì)象不是Promise對(duì)象语盈,該方法會(huì)創(chuàng)建一個(gè)Promise對(duì)象
- 如果目標(biāo)對(duì)象本身就是Promise對(duì)象舱馅,該方法會(huì)將這個(gè)Promise對(duì)象直接返回
// p2和p1行為完全一樣
var p1 = new Promise(function(resolve, reject){
resolve(40);
});
var p2 = Promise.resolve(40);
// 向Promise.resolve()傳遞一個(gè)Promise對(duì)象,則直接返回這個(gè)對(duì)象
var p3 = Promise.resolve(p2);
console.log(p2===p3); // true
(4) Promise.reject()
返回一個(gè)新的Promise實(shí)例黎烈,狀態(tài)為rejected:
var p = new Promise(function(resolve, reject){
reject("error");
});
//可以用 Promise.reject()快速創(chuàng)建promise對(duì)象习柠,狀態(tài)為rejected
var p = Promise.reject("error");
p.then(null, function(error) {
// 回調(diào)函數(shù)被調(diào)用
});