什么是Promise
Promise是抽象異步處理對(duì)象以及對(duì)其進(jìn)行各種操作的組件。 其詳細(xì)內(nèi)容在接下來(lái)我們還會(huì)進(jìn)行介紹,Promise并不是從JavaScript中發(fā)祥的概念。
Promise最初被提出是在 E語(yǔ)言中, 它是基于并列/并行處理設(shè)計(jì)的一種編程語(yǔ)言。
現(xiàn)在JavaScript也擁有了這種特性,這就是本書(shū)所介紹的JavaScript Promise。
另外,如果說(shuō)到基于JavaScript的異步處理,我想大多數(shù)都會(huì)想到利用回調(diào)函數(shù)。
Promise解決的問(wèn)題
我相信每個(gè)前端都遇到過(guò)這樣一個(gè)問(wèn)題,當(dāng)一個(gè)異步任務(wù)的執(zhí)行需要依賴(lài)另一個(gè)異步任務(wù)的結(jié)果時(shí),我們一般會(huì)將兩個(gè)異步任務(wù)嵌套起來(lái),這種情況發(fā)生一兩次還可以忍,但是發(fā)生很多次之后,你的代碼就會(huì)變成這個(gè)熊樣:
async1(function(){
async2(function(){
async3(function(
async4(funciton(){
async5(function(){
//(╯°□°)╯︵┻━┻
//...
});
});
));
});
});
這就是所謂的回調(diào)地獄,代碼層層嵌套,環(huán)環(huán)相扣,很明顯,邏輯稍微復(fù)雜一些,這樣的程序就會(huì)變得難以維護(hù)。
但去年ES2015的標(biāo)準(zhǔn)里,Promise的標(biāo)準(zhǔn)化,一定程度上解決了JavaScript的流程操作問(wèn)題。
Promise的基本用法
Promise類(lèi)似于 XMLHttpRequest,從構(gòu)造函數(shù) Promise 來(lái)創(chuàng)建一個(gè)新建新promise對(duì)象作為接口。
要想創(chuàng)建一個(gè)promise對(duì)象、可以使用new來(lái)調(diào)用Promise的構(gòu)造器來(lái)進(jìn)行實(shí)例化。
我們新建一個(gè)Promise的實(shí)例:
var _promise = new Promise(function(resolve, reject){
setTimeout(function(){
var rand = Math.random();
if(rand<0.5){
resolve("resolve"+rand);
}else{
reject("reject"+rand);
}
}, 1000)
})
運(yùn)行結(jié)果有兩種情況,如下圖

先跟大家介紹一下Promsie的基礎(chǔ)知識(shí)
Promise對(duì)象有三種狀態(tài),他們分別是:
- pending:等待中或者進(jìn)行中,表示還沒(méi)得到結(jié)果
- resolve(fulfilled):已經(jīng)完成,表示得到了我們想要的結(jié)果,可以繼續(xù)往下執(zhí)行
- rejected:也表示得到結(jié)果,但是由于結(jié)果并非我們希望的,因此拒絕執(zhí)行
這三種狀態(tài)不受外界影響,而且狀態(tài)只能從pending改變?yōu)閞esolved或者rejected,并且不可逆。
在Promise對(duì)象的構(gòu)造函數(shù)中,接受一個(gè)函數(shù)作為參數(shù),該函數(shù)就是用來(lái)處理Promise的狀態(tài)變化的,該函數(shù)接受兩個(gè)額外的函數(shù)resolve和reject,這兩個(gè)函數(shù)分別代表將當(dāng)前Promise置為fulfilled(解決)和rejected(拒絕)兩個(gè)狀態(tài)。
實(shí)際上Promise實(shí)例_promise是一個(gè)對(duì)象,不是一個(gè)函數(shù)。在聲明的時(shí)候,Promise傳遞的參數(shù)函數(shù)會(huì)立即執(zhí)行,因此Promise使用的正確姿勢(shì)是在其外層再包裹一層函數(shù)。如下:
var run = funciton(){
var _promise = new Promise(function(resolve, reject){
setTimeout(function(){
var rand = Math.random();
if(rand<0.5){
resolve("resolve"+rand);
}else{
reject("reject"+rand);
}
},1000)
})
return _promise;
}
run();
這是Promise的正常用法
一個(gè)Promise必須提供一個(gè)then方法來(lái)獲取其值或原因。
Promise的then方法接受兩個(gè)參數(shù):promise.then(onFulfilled, onRejected)
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ù)。
接著上面創(chuàng)建的函數(shù)run(),我們來(lái)進(jìn)行對(duì)異步操作結(jié)果的處理:
run().then(function(data){
//處理resolve的代碼
console.log("Promise被置為resolve",data);
}, function(data){
//處理reject的代碼
console.log("Promise被置為reject",data);
})
如果異步操作獲得了我們想要的結(jié)果,那我們將調(diào)用處理resolve的函數(shù)(即onFulfilled方法),也就是在then的第一個(gè)作為參數(shù)的函數(shù)中進(jìn)行處理,獲取數(shù)據(jù);如果我們得到了錯(cuò)誤的結(jié)果,調(diào)用處理reject的函數(shù)(即 onRejected方法),也就是在then函數(shù)的第二個(gè)作為參數(shù)的函數(shù)中進(jìn)行處理,獲取錯(cuò)誤處理數(shù)據(jù)。
對(duì)reject后的處理,除了可以在.then 的第二個(gè)參數(shù)中處理,還可以在 .catch 方法中設(shè)置想要調(diào)用的函數(shù)。
以下是將對(duì)reject的處理放到.catch方法中:
run().then(function(data){
//處理resolve的代碼
console.log("Promise被置為resolve",data);
}).catch(function(data){
//處理reject的代碼
console.log("Promise被置為reject",data);
})
這樣,一個(gè)次完整的Promise調(diào)用就結(jié)束了。對(duì)于Promise的then()方法,then總是會(huì)返回一個(gè)Promise實(shí)例:
promise2 = promise1.then(onFulfilled, onRejected);
因此我們可以像上面面那樣接著對(duì)其返回值進(jìn)行 .then 調(diào)用,形如run().then().then().then().then().then().....
在一個(gè)then()方法調(diào)用異步處理成功的狀態(tài)時(shí),你既可以return一個(gè)確定的“值”,也可以再次返回一個(gè)Promise實(shí)例,當(dāng)返回的是一個(gè)確切的值的時(shí)候,then會(huì)將這個(gè)確切的值傳入一個(gè)默認(rèn)的Promise實(shí)例,并且這個(gè)Promise實(shí)例會(huì)立即置為fulfilled狀態(tài),以供接下來(lái)的then方法里使用。如下所示:
run().then(function(data){
console.log("第一次",data);
return data;
}).then(function(data){
console.log("第二次",data);
return data;
}).then(function(data){
console.log("第三次",data);
return data;
});
/* 異步處理成功的打印結(jié)果:
第一次 resolve0.49040459200760167d.js:18
第二次 resolve0.49040459200760167d.js:21
第三次 resolve0.49040459200760167
由此可知then方法可以無(wú)限調(diào)用下去。
*/
根據(jù)這個(gè)特性,我們就可以將相互依賴(lài)的多個(gè)異步邏輯,進(jìn)行順序管理。
下面舉個(gè)例子:
//第一個(gè)異步任務(wù)
function run_a(){
return new Promise(function(resolve,reject){
//假設(shè)已經(jīng)進(jìn)行了異步操作,并獲得了數(shù)據(jù)
resolve("step1");
})
}
//第二個(gè)異步任務(wù)
function run_b(){
return new Promise(function(resolve,reject){
//假設(shè)已經(jīng)進(jìn)行了異步操作,并獲得了數(shù)據(jù)
resolve("step2");
})
}
//第三個(gè)異步任務(wù)
function run_c(){
return new Promise(function(resolve,reject){
//假設(shè)已經(jīng)進(jìn)行了異步操作,并獲得了數(shù)據(jù)
resolve("step3");
})
}
//連續(xù)調(diào)用
run_a().then(function(data){
//返回一個(gè)Promise實(shí)例
return run_b(data);
}).then(function(data){
return run_c(data);
}).then(function(data){
console.log(data);
})
- 阮一峰的Promise科普文
- promises-book(非常推薦大家閱讀的資料,對(duì)Promise講解十分細(xì)致詳盡)