如果瀏覽已經(jīng)有了Promise對象,那么頁就說明瀏覽器的js引擎里已經(jīng)有了Promsise隊列,這樣就可以利用Promise將任務(wù)放在它的隊列中
目前在瀏覽器中大部分已經(jīng)支持了Promise,除了IE,Promise是在ES6中javascript實現(xiàn)了prmose規(guī)范,和JQuery中的$.Deferred大概思路是一樣的,可能是部分API的用法有差異
Promise對象有三種狀態(tài),分別是
- pending:等待/進行中,表示還沒有得到結(jié)果
- resolved:已經(jīng)完成,表示得到了我們想要的結(jié)果,允許繼續(xù)向下執(zhí)行
- rejected:表示結(jié)果錯誤,拒絕執(zhí)行
這三種狀態(tài)不受外界影響,而且狀態(tài)只能從pending改變?yōu)閞esolved或者rejected,并且不可逆,在Promise對象的構(gòu)造函數(shù)中,將一個函數(shù)作為第一個參數(shù),而這個函數(shù)就是用來處理Promise的狀態(tài)變化
new Prmise(function(resolve,reject){
if(true){ resolve(msg)};
if(false){ reject(msg)};
})
上面的resolve和reject都是一個函數(shù),他們的作用分別是將狀態(tài)修改為reslolved和rejected,resolve用于在異步操作成功時調(diào)用,并將異步操作的結(jié)果作為參數(shù)傳遞出去,reject會在異步操作失敗時調(diào)用,并將異步操作報出的錯誤作為參數(shù)傳遞出去
promise實例生成以后,可以使用then方法分別指定resolved狀態(tài)和rejectd狀態(tài)的回調(diào)函數(shù),該方法接收兩個回調(diào)函數(shù)作為參數(shù),第一個回調(diào)函數(shù)是promise對象的狀態(tài)變?yōu)閞esolved時調(diào)用,第二個回調(diào)函數(shù)是在promise對象狀態(tài)變?yōu)閞ejected時調(diào)用,其中第二個函數(shù)是可選的,這兩個函數(shù)都接收promise對象傳出的值作為參數(shù),注意一點promise中的方法會立即執(zhí)行,不會進入隊列,但是then中的方法會延后執(zhí)行,then中的方法會進入微隊列,在本次隊列的其它函數(shù)執(zhí)行完成之后再執(zhí)行
let promise=new Promise(function (resolve,reject) {
console.log('promsie');
resolve()
});
promise.then(function () {
console.log('resolved');
})
console.log("HI");
//promise
//HI
//resolved
注意,調(diào)用resolve或reject并不會終結(jié)promise的參數(shù)的執(zhí)行,因為我們在在函數(shù)中調(diào)用resolve,resolve是在本輪事件循環(huán)末尾,是晚于本輪同步循環(huán)任務(wù)的,而且,一般來說,調(diào)用resolve或reject后,promise的使命就已經(jīng)完成了,不應該再在他后面寫操作,而應該吧操作放置到then中,所以一般會在調(diào)用resolve或reject時在它們前面加上return語句,防止意外
new Promise((resolve,reject)=>{
return resolve("data");
console.log("hello");//如果不添加return,那么hello仍然會輸出
})
.then(data=>{
console.log(data);
})
reject函數(shù)的參數(shù)通常是Error對象的實例,表示拋出錯誤;resolve函數(shù)的參數(shù)除了正常的值以外,還可能是另一個promise實例
let p1=new Promise((resolve,reject)=>{
setTimeout(()=>reject(new Error('fail')),2000)
})
let p2=new Promise((resolve,reject)=>{
resolve(p1)
})
p2.then(success=>console.log(success))
.catch(error=>console.error(error))
//Error: fail
在接收p1之后,因為p1本身是一promise對象,所以在p2的狀態(tài)會被p1替換掉,p2自身的狀態(tài)無效了,由p1返回的狀態(tài)決定p2的狀態(tài),所以,p2的.then方法的觸發(fā)條件都會由p1來決定
下面用promise對象實現(xiàn)一個getJSON函數(shù)
var getJSON = function (url) {
var promise = new Promise(function (resolve, reject) {
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "qpplication/json");
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.state === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
}
getJSON("/posts.json").then(function (data) {
console.log(data);
}, function (error) {
console.log(error);
})
Promise.prototype.then()
then方法是定義在原型對象Promise.prototype上的,需要注意的是then方法返回的是一個新的promise實例,所以可以采用鏈式寫法,在第一個回調(diào)函數(shù)完成以后,會將返回結(jié)果作為參數(shù),傳入第二個回調(diào)函數(shù)
new Promise((resolve,reject)=>{
resolve(data)
})
.then(data=>{
console.log(data)
return success;
})
.then(data=>{
console.log(data)
})
// data
// success
采用鏈式的then,可以指定一組按照次序調(diào)用的回調(diào)函數(shù),如果前一個函數(shù)返回的是一個promise對象,后一個回調(diào)函數(shù)就會等待該Promise對象的狀態(tài)發(fā)生變化才會被調(diào)用
Promise.prototype.catch()
該方法是.then(null,rejection)的別名,用于指定發(fā)生錯誤時的回調(diào)函數(shù),另外then方法指定的回調(diào)函數(shù),如果運行中拋出錯誤,也會被catch方法捕獲,Promise對象的錯誤具有"冒泡"性質(zhì),會一直向后傳遞,直到該錯誤被捕獲
new Promise((resolve, reject) => {
resolve('success')
reject("promise對象運行錯誤") //在已經(jīng)調(diào)用了resolve的情況下之后的reject不會起作用,該錯誤也不會被捕獲
//因為我們之前已經(jīng)了解promise對象的狀態(tài)一旦改變就不會再次發(fā)生變化
})
.catch(error=>console.error(error))
.then(data => {
console.log(data);
throw new Error("then運行錯誤")
})
.catch(error=>console.error(error))
//success
//then運行錯誤
一般來說,我們不會在then中定義reject狀態(tài)的回調(diào)函數(shù),總是使用catch方法,因為使用catch方法不僅會捕獲當前promise對象的錯誤,也會捕獲之前錯誤
我們可以觀察下面的代碼
.catch(error=>console.error(error))
.then(null,error=>console.log(error))
//以上代碼是等價的
try {
x+2
}catch(e){
reject(e)
}
reject(new Error("x is not defined"))
//以上代碼是等價的
//可以理解為reject的作用就是拋出錯誤
和傳統(tǒng)的try/catch代碼塊不同的是,如果沒有使用catch方法指定錯誤處理的回調(diào)函數(shù),promise對象拋出的錯誤不會傳遞到外層代碼,也不會有任何反應,Promise內(nèi)部的錯誤不會影響到Promise外部的代碼,通俗的說就是Promise會吃掉錯誤,這是我們需要注意的,所以就要求我們?nèi)绻覀働romise中代碼會對后續(xù)代碼有影響,一定注意要在Promise對象后面跟catch方法,這樣可以處理Promise內(nèi)部發(fā)生的錯誤,并且catch方法返回的還是一個Promise對象,因此后面還可以接著調(diào)用then方法,如果沒有報錯會跳過catch方法
new Promise((resolve,reject)=>resolve(x+2))
.catch(error=>{
if(error) {
throw new Error(error)
}
})
.then(data=>console.log(data));
console.log("下一個操作");
//如果沒有catch方法來拋出錯誤
//瀏覽器在運行到x+2時會報出 x is not defined 的錯誤
//但不會停止程序,下面的操作不受影響
Promise.all()
Promise.all()方法用于將多個Promise實例,包裝成一個新的Promise實例,類似于JQuery中$.when()方法,但不同之處在于,Promise.all()會自動將其中不是Promise實例的對象轉(zhuǎn)化為Promise實例對象然后再執(zhí)行函數(shù)
- 如果其中有一個promise對象的狀態(tài)為pending,那么all的會等待其狀態(tài)變更后再執(zhí)行
- 如果其中有一個promise對象的狀態(tài)為rejected,那么all的狀態(tài)就會變成rejected,并將第一個變?yōu)閞ejected狀態(tài)的實例的返回值會傳遞給all的回調(diào)函數(shù)
- 只有所有的promise對象的狀態(tài)都是resolved,all的狀態(tài)才會變成resolved,此時所有傳入的promise對象的返回值組成一個數(shù)組返回給all的回調(diào)函數(shù)
const p1 = new Promise((resolve, result) => {
console.log("p1");
resolve();
})
const p2 = new Promise((resolve, result) => {
console.log("p2");
resolve();
})
.then(() => {
console.log("p2執(zhí)行完畢");
return 2;
})
const p3 = function () {
console.log("p3執(zhí)行完畢");
}
Promise.all([p1, p2, p3])
.then((success) => {
console.log(success);
})
如果在Promise對象自身有then或catch方法,那么會優(yōu)先執(zhí)行每一個promise對象的then和catch方法,因為then和catch方法會返回一個promise對象,所以在執(zhí)行完自身then和catch方法后會繼續(xù)執(zhí)行Promise.all的then和catch方法,如果當前的promise對象不存在then/catch方法,那么會調(diào)用Promise.all的then和catch方法,這里需要注意一點的是參數(shù)問題,如果調(diào)用了自身的then或catch方法,那么在Promise.all方法中接收的參數(shù)是then/catch中返回的值
Promise.race()
該方法和Promise.all()一樣,同樣是將多個promise實例包裝成一個新的Promise實例
區(qū)別在于在Promise.race()中只要其中一個參數(shù)的狀態(tài)發(fā)生改變,race的狀態(tài)就會發(fā)生改變,并將第一改變狀態(tài)的promise實例的返回值作為參數(shù)傳遞給race的回調(diào)函數(shù)
利用這個特性我們可以做一個如果函數(shù)執(zhí)行時間超過我們設(shè)定的時間后報錯的函數(shù)
Promise.race([
$.get('test.html',(res)=>{
console.log(res)
}),
new Promise((resolve,reject)=>{
setTimeout(()=>reject(new Error('request timeout')),5000)
})
])
.catch(error=>console.log(error))
.then(success=>console.log(success))
Promise.resolve()
有時需要將現(xiàn)有對象轉(zhuǎn)化為Promise對象,就可以使用Promise.resolve()方法,返回值為新的Promise對象,我們在使用Promise.all()方法和Promise.race()方法時,這兩個方法對于我們傳入的不是promise實例的對象也是默認使用了Promise.resolve方法對其中的對象進行了轉(zhuǎn)換
例如我們要將一個JQuery的deferred對象轉(zhuǎn)化為一個新的Promise對象
var promise=Promise.resolve($.ajax('./test.txt'))
//上面的方法其實等價于下面的寫法
var promise=new Promise(resolve=>resolve($.ajax('./text.txt')))
Promise.resolve方法會根據(jù)傳入的參數(shù)不同分為四種情況
-
參數(shù)是一個promise實例
那么Promise.resolve將不會做任何修改,直接返回該實例
-
參數(shù)是一個具有then方法的thenable對象
thenable對象就是指那些具有then方法名的對象,Promise.resolve()在接收該方法后會將該對象轉(zhuǎn)化為promise對象,然后立即執(zhí)行該對象的then方法
var thenable={ then(){ console.log("then方法被執(zhí)行啦"); } } Promise.resolve(thenable) //then方法被執(zhí)行啦
-
如果參數(shù)不是promise對象,并且不具有then方法,那么回返回一個新的promise對象,狀態(tài)為resolved,并將自身返回給回調(diào)函數(shù)
let p={ name:'tom' } p = Promise.resolve(p); p.then(function (res) { console.log(res); }) //{name:'tom'}
-
不接收任何參數(shù)
let p=Promise.resolve(); p.then(function(){ //... })
Promise.reject()
同Promise.resolve()的用法相同,不同的是會返回一個狀態(tài)為rejected的實例,回調(diào)函數(shù)會立即執(zhí)行
需要注意一點,Promise.reject()方法的參數(shù),會不做任何改變的作為回調(diào)函數(shù)的參數(shù)傳入,這也是兩個方法的區(qū)別所在
let thenable={
then(resolve,reject){
reject("error")
}
}
Promise.reject(thenable)
.catch(err=>console.log(err === thenable))
//true
上面的代碼最后的回調(diào)函數(shù)是將thenable方法整體作為參數(shù)接收了,而不是我們傳入的error