promise是什么
promise對(duì)象可以理解為一次執(zhí)行的一步操作,使用promise對(duì)象之后可以用一種鏈?zhǔn)秸{(diào)用的方式來(lái)組織代碼,使代碼更加的直觀!而且由于有promise.all 這樣的方法存在,可以讓同事執(zhí)行多個(gè)操作變得簡(jiǎn)單。
為什么要使用promise?舉個(gè)栗子。有一個(gè)需求,多個(gè)接口異步請(qǐng)求數(shù)據(jù),然后利用這些數(shù)據(jù)來(lái)進(jìn)行一系列的操作。代碼如下:
$.ajax({
url:'a.php',
datatype:"json",
success:function(data){
// 獲取data數(shù)據(jù) 傳給下一個(gè)請(qǐng)求
var id=data.id;
$.ajax({
url:"b.php",
datatype:'json',
data:{"id":id},
success:function(){
}
})
}
})
這樣的寫(xiě)法的原理是,當(dāng)執(zhí)行一些一步操作的時(shí)候,我們需要知道操作是不是完成了,所以當(dāng)執(zhí)行完成的時(shí)候會(huì)返回一個(gè)回調(diào)函數(shù),然我們知道操作已經(jīng)完成了,但是,這樣做有幾個(gè)缺點(diǎn):
- 在需要多個(gè)操作的時(shí)候,會(huì)導(dǎo)致回調(diào)函數(shù)的嵌套,代碼不夠直觀,這就是地獄回調(diào)(Callback hell)
- 如果前后兩個(gè)參數(shù)在不需要傳參的情況下,同樣需要等待上一個(gè)操作完成再實(shí)行下一個(gè)操作
因此針對(duì)這種情況下,我們可以使用promise對(duì)象來(lái)解決上面的問(wèn)題了~
如何創(chuàng)建promise
可以用new 來(lái)調(diào)用promise的構(gòu)造器來(lái)進(jìn)行實(shí)例化,如代碼:
new promise(function(resolve,reject){
//異步請(qǐng)求,
//成功調(diào)用resolve 往下傳遞參數(shù) 且只接受一個(gè)參數(shù)
//失敗調(diào)用reject 往下傳遞參數(shù) 且只接受一個(gè)參數(shù)
})
對(duì)通過(guò)new 生成的promise對(duì)象為了設(shè)置其值在resolve(成功) / reject(失敗) 時(shí)調(diào)用的回調(diào)函數(shù),可以使用promise.then()實(shí)例方法。
代碼如下:
promise.then(onFulfilled, onRejected)
resolve(成功)時(shí)調(diào)用onFulfilled方法,reject(失敗) 時(shí) 調(diào)用onRejected方法;
Promise.then, 成功和失敗時(shí)都可以使用,如果出現(xiàn)異常的情況下可以采用
promise.then(undefined,onRejected) 這種方式,只指定onRejected回調(diào)函數(shù)即可,不過(guò)針對(duì)這種情況下我們有更好的選擇是使用catch這個(gè)方法;代碼如下:
promise.catch(onRejected);
resolve 和 reject
首先看下面promise 的代碼:
function helloWorld (ready) {
return new Promise(function (resolve, reject) {
if (ready) {
resolve("Hello World!");
} else {
reject("Good bye!");
}
});
}
helloWorld(true).then(function (message) {
alert(message);
}, function (error) {
alert(error);
});
helloWorld方法接受一個(gè)參數(shù),如果為true 打印 Hello World!如果為false打印Good bye!;helloWorld()返回的是一個(gè)promise對(duì)象。
在promise中有兩個(gè)很重的方法:resolve 和 reject;
==resolve== :可以使 Promise 對(duì)象的狀態(tài)改變成成功,同時(shí)傳遞一個(gè)參數(shù)用于后續(xù)成功后的操作,在這個(gè)例子當(dāng)中就是 Hello World! 字符串。
==reject== :可以使 Promise 對(duì)象的狀態(tài)改變成失敗,同時(shí)將錯(cuò)誤的信息傳遞到后續(xù)錯(cuò)誤處理的操作。
理解Promise.resolve和理解Promise.reject
一般情況下我們都會(huì)使用new Promise()來(lái)創(chuàng)建promise對(duì)象,但是我們也可以使用promise.resolve 和 promise.reject這兩個(gè)方法;
Promise.resolve(value)的返回值也是一個(gè)promise對(duì)象,我們可以對(duì)返回值進(jìn)行.then調(diào)用;如下代碼:
promise.resolve(10).then(function(value){
console.log(value)//10
})
resolve(10)代碼中,會(huì)讓promise對(duì)象進(jìn)入確定(resolve狀態(tài)),并將參數(shù)11傳遞給后面的then所指定的onFulfilled 函數(shù);
Promise.reject 也是new Promise的快捷形式,也創(chuàng)建一個(gè)promise對(duì)象,比如如下代碼:
new Promise(function(resolve,reject){
reject(new Error("錯(cuò)誤!"));
});
可以變成:
Promise.reject(new Error("錯(cuò)誤!"));
綜合看看使用resolve方法和reject方法的demo如下:
function getready(ready){
return new Promise(function(resolve,reject){
if(ready){
resolve("ok")
}else{
reject("No");
}
})
};
getready(true).then(function(msg){
console.log(msg)
},function(error){
console.log(error)
})
上面的代碼的含義是給getready方法傳遞一個(gè)參數(shù),返回的是一個(gè)promise對(duì)象,如果為true的話,那么調(diào)用promise對(duì)象中的resolve()方法,并且把其中的參數(shù)傳遞給后面的then第一個(gè)函數(shù)內(nèi),因此打印出 ok, 如果為false的話,會(huì)調(diào)用promise對(duì)象中的reject()方法,則會(huì)進(jìn)入then的第二個(gè)函數(shù)內(nèi),會(huì)打印No;
Promise對(duì)象的三種狀態(tài)
- Fulfilled 可以理解為成功的狀態(tài)
- Rejected 可以理解為失敗的狀態(tài)
- Pending 既不是 Fulfilld 也不是 Rejected 的狀態(tài),可以理解為 Promise 對(duì)象實(shí)例創(chuàng)建時(shí)候的初始狀態(tài)
比如Promise對(duì)象中的resolve方法就是調(diào)用then對(duì)象的第一個(gè)函數(shù),也就是成功的狀態(tài);而reject方法就是調(diào)用then對(duì)象的第二個(gè)函數(shù),也就是失敗的狀態(tài);
理解Promise異步調(diào)用的操作
如以下代碼:
var promise = new Promise(function(resolve){
console.log(1);
resolve(3);
});
promise.then(function(value){
console.log(value);
});
console.log(2);
//輸出順序:1 2 3
上面的代碼輸出結(jié)果為: 1,2,3;首先代碼從上往下執(zhí)行,首先輸出的1,然后調(diào)用resolve(3)這個(gè)方法,這個(gè)時(shí)候,Promise變?yōu)榱舜_定狀態(tài),即調(diào)用onFullfilled的方法,Promise.then() 成功與否都可以使用的,因此第一個(gè)函數(shù)是成功調(diào)用,但是promise 是以異步方式調(diào)用的,所以先執(zhí)行,console.log(2),最后輸出3
理解then()
在上面 getready()的例子當(dāng)中利用了 then(onFulfilld, onRejected) 方法來(lái)執(zhí)行一個(gè)任務(wù)打印 "ok",在多個(gè)任務(wù)的情況下 then 方法同樣可以用一個(gè)清晰的方式完成。
function printHello (ready) {
return new Promise(function (resolve, reject) {
if (ready) {
resolve("Hello");
} else {
reject("Good bye!");
}
});
}
function printWorld () {
alert("World");
}
function printExclamation () {
alert("!");
}
printHello(true)
.then(function(message){
alert(message);
})
.then(printWorld)
.then(printExclamation);
上述例子通過(guò)鏈?zhǔn)秸{(diào)用的方式,按順序打印出了相應(yīng)的內(nèi)容。then 可以使用鏈?zhǔn)秸{(diào)用的寫(xiě)法原因在于,每一次執(zhí)行該方法時(shí)總是會(huì)返回一個(gè) Promise 對(duì)象。另外,在 then onFulfilled 的函數(shù)當(dāng)中的返回值,可以作為后續(xù)操作的參數(shù),因此上面的例子也可以寫(xiě)成:
function printHello (ready) {
return new Promise(function (resolve, reject) {
if (ready) {
resolve("Hello");
} else {
reject("Good bye!");
}
});
}
function printWorld () {
alert("World");
}
function printExclamation () {
alert("!");
}
printHello(true).then(function (message) {
return message;
}).then(function (message) {
return message + ' World';
}).then(function (message) {
return message + '!';
}).then(function (message) {
alert(message);
});
//輸出Hellow world!
var promise1 = new Promise(function(resolve){
resolve(2);
});
promise1.then(function(value){
return value * 2;
}).then(function(value){
return value * 2;
}).then(function(value){
console.log("1"+value);
});
打印出18,即 "1" + 222 = 18;
使用方法鏈的then,使多個(gè)then方法連接在一起了,因此函數(shù)會(huì)嚴(yán)格執(zhí)行 resolve -- then --- then -- then的順序執(zhí)行,并且傳遞每個(gè)then方法的value的值都是前一個(gè)promise對(duì)象中return的值;因此最后的結(jié)果就是18了;
理解catch()
Promise.catch()方法是promise.then(undefined,onRejected)方法的一個(gè)別名,該方法用來(lái)注冊(cè)當(dāng)promise對(duì)象狀態(tài)變?yōu)镽ejected的回調(diào)函數(shù)。
如下代碼:
var promise = Promise.reject(new Error("message"));
promise.catch(function(error){
console.log(error.message);//message
});
現(xiàn)在我們?cè)倩剡^(guò)頭一剛開(kāi)始我們討論的為什么要使用promise的原因的問(wèn)題了,比如2個(gè)ajax請(qǐng)求,后一個(gè)ajax請(qǐng)求需要獲取到前一個(gè)ajax請(qǐng)求的數(shù)據(jù),我們之前在使用jquery寫(xiě)代碼是如下的:
$.ajax({
url:'a.php',
datatype:"json",
success:function(data){
// 獲取data數(shù)據(jù) 傳給下一個(gè)請(qǐng)求
var id=data.id;
$.ajax({
url:"b.php",
datatype:'json',
success:function(){
}
})
}
})
現(xiàn)在我們學(xué)習(xí)了then方法后,我們可以重新編寫(xiě)上面的代碼變成如下:
var ajaxPromise = new Promise(function(resolve){
resolve();
});
ajaxPromise.then(fucntion(){
$.ajax({
url:'a.php',
datatype:"json",
success:function(data){
// 獲取data數(shù)據(jù) 傳給下一個(gè)請(qǐng)求
var id=data.id;
return id;
}
})
}).then(function(id){
$.ajax({
url:'',
dataType:'json',
data:{"id":id},
success: function(data){
console.log(data);
}
});
})
理解Promise.all
Promise.all可以接受一個(gè)元素為Promise對(duì)象的數(shù)組作為參數(shù),當(dāng)這個(gè)數(shù)組里面所有的promise對(duì)象都變?yōu)閞esolve時(shí),該方法才會(huì)返回。
如下代碼:
var promise1 = new Promise(function(resolve){
setTimeout(function(){
resolve(1);
},3000);
});
var promise2 = new Promise(function(resolve){
setTimeout(function(){
resolve(2);
},1000);
});
Promise.all([promise1,promise2]).then(function(value){
console.log(value); // 打印[1,2]
});
輸出的是1,2。如上我們看到promise1對(duì)象中的setTimeout是3秒的時(shí)間,而promise2對(duì)象中的setTimeout是1秒的時(shí)間,但是在Promise.all方法中會(huì)按照數(shù)組的原先順序?qū)⒔Y(jié)果返回;在我們平時(shí)的需求中,或許有這種情況的需求,比如我們需要發(fā)2個(gè)ajax請(qǐng)求時(shí),不管他們的先后順序,當(dāng)這2個(gè)ajax請(qǐng)求都同時(shí)成功后,我們需要執(zhí)行某些操作的情況下,這種情況非常適合;
理解Promise.all
如上可知:Promise.all 在接收到的所有對(duì)象promise都變?yōu)镕ulFilled或者 Rejected狀態(tài)之后才會(huì)繼續(xù)后面的處理,但是Promise.race的含義是只要有一個(gè)promise對(duì)象進(jìn)入FulFilled或者Rejected狀態(tài)的話,程序就會(huì)停止,且會(huì)繼續(xù)后面的處理邏輯;
如下代碼:
// `delay`毫秒后執(zhí)行resolve
function timerPromise(delay){
return new Promise(function(resolve){
setTimeout(function(){
resolve(delay);
},delay);
});
}
// 任何一個(gè)promise變?yōu)閞esolve或reject 的話程序就停止運(yùn)行
Promise.race([
timerPromise(1),
timerPromise(32),
timerPromise(64),
timerPromise(128)
]).then(function (value) {
console.log(value); // => 1
});
輸出的是1,如上代碼創(chuàng)建了4個(gè)promise對(duì)象,這些promise對(duì)象分別在1ms,32ms,64ms,128ms后變?yōu)榇_定狀態(tài),并且在第一個(gè)變?yōu)榇_定狀態(tài)后1ms后,then函數(shù)就會(huì)被調(diào)用,這時(shí)候resolve()方法給傳遞的值為1,因此執(zhí)行then的回調(diào)函數(shù)后,值變?yōu)?;
我們?cè)賮?lái)看看當(dāng)一個(gè)promise對(duì)象變?yōu)榇_定狀態(tài)(FulFiled)的時(shí)候,他們后面的promise對(duì)象是否還在運(yùn)行呢?我們繼續(xù)看如下代碼運(yùn)行:
var runPromise = new Promise(function(resolve){
setTimeout(function(){
console.log(1);
resolve(2);
},500);
});
var runPromise2 = new Promise(function(resolve){
setTimeout(function(){
console.log(3);
resolve(4);
},1000);
});
// 第一個(gè)promise變?yōu)閞esolve后程序停止
Promise.race([runPromise,runPromise2]).then(function(value){
console.log(value);
});
輸出的是: 1 2 3,。
如上代碼是使用定時(shí)器調(diào)用的,上面是2個(gè)promise對(duì)象,我們看到第一個(gè)promise對(duì)象過(guò)500毫秒后加入到執(zhí)行隊(duì)列里面去,如果執(zhí)行隊(duì)列沒(méi)有其他線程在運(yùn)行的時(shí)候,就執(zhí)行該定時(shí)器,所以第一次打印1,然后調(diào)用resolve(2); 接著調(diào)用promise.race方法,該方法只要有一個(gè)變?yōu)槌晒顟B(tài)(FulFiled)的時(shí)候,程序就會(huì)停止,因此打印出2,同時(shí)后面的promise對(duì)象接著執(zhí)行,因此打印出3,但是由于promise.race()該方法已經(jīng)停止調(diào)用了,所以resolve(4)不會(huì)有任何輸出;因此最后輸出的是1,2,3;
由此我們得出結(jié)論,當(dāng)一個(gè)promise對(duì)象變?yōu)?FulFilled)成功狀態(tài)的時(shí)候,后面的promise對(duì)象并沒(méi)有停止運(yùn)行。