es6的Promise物件詳解
秋招已經進入了尾聲,這兩個月以來一直忙著找工作,沒有時間看新東西,這幾天得空將es6的Promise物件又細細的理解了一下,所以下來整理一下我對Promise物件以及用法的理解。
什麼是Promise?為什麼要使用Promise?
當我們使用js的非同步呼叫時通常習慣使用回撥函式,這樣的程式碼簡單便於理解,但是當回撥巢狀的時候就會造成程式碼混亂,不好梳理,比如:
function fun1(arg1,function(){
// dosomething
})
這樣簡單的巢狀自然是沒問題,還便於理解。但是當有多層巢狀的時候:
我們只知道Promise是最大的好處是為了避免“回撥地獄”,就是多層的回撥
function fun1(arg1,function(data1){
function fun1(arg2, function(data2){
function fun1(arg3, function(data3){
//.....
})
})
})
像上面這樣,三層巢狀就已經很麻煩了,更何況更多層的巢狀,所以為了讓回撥更明顯,程式碼更容易理解,使用Promise可以很優雅的解決這個問題:
var p = new Promise(function(resolve,reject){
}).then(function(data){
}).then(function(data){
}).then……
上面就是Promise的基本用法,Promise接受一個回撥函式,回撥函式接收兩個引數,resolve(將Promise的狀態從pending變為fulfilled,在非同步操作成功時呼叫,並將非同步操作的結果傳遞出去)、reject(將Promise的狀態從pending變為rejected,在非同步操作失敗時呼叫,將非同步操作的錯誤作為引數傳遞出去)這兩個都是函式,表示成功和失敗的處理函式。then中接受的是上一次回撥返回的結果,所以這樣的鏈式呼叫就可以完全清晰的實現多層呼叫。
Promise物件有兩個特點:
①,物件的狀態不受外界的影響,Promise有三種狀態:Pending(進行中)、fulfilled(已成功)、rejected(失敗),只用非同步操作的結果可以決定當前是哪一種狀態,其他任何操作都無法改變這個操作。
②.一旦狀態改變之後就不會再改變。任何時候都可以得到這個結果。
Promise的缺點:
① 一旦建立就無法取消,一旦新建就會立即執行
② 如果不設定回撥函式,它的內部錯誤就不會反映到外部。
③ 當處於pending狀態時,無法判斷進展到哪一階段(剛開始還是快完成)。
接下來講一下Promise的基本語法,先看一下Promise打印出來的結果
從上圖可以看到Promise.prototype上有catch、then、constructor方法。所以這幾個方法可以被例項繼承。
Promise自身會有Promise.all()、Promise.race()、Promise.resolve()、Promise.reject()一些常用的方法。
1.Promise.prototype.then()
let promise = new Promise(function(resolve,reject){
console.log("promise");
resolve();
});
setTimeout(function(){
console.log("setTimeout");
},0)
promise.then(function(){
console.log("resolved");
})
console.log("hi");
// promise hi resolved setTimeout
上面的程式碼很好的驗證了,promise是建立之後立即執行,then方法指定的指令碼在當前的所有同步任務完成之後再執行,setTimeout是在下一輪“時間迴圈”開始時執行,then在本輪事件迴圈結束時執行。
2.Promise.prototype.catch()
當Promise物件執行成功時使用的是resolve回撥函式,進而在then方法中進一步處理,當promise物件失敗時在那兒進行處理?
有兩種方法:①在then方法接受第二個函式引數,用來處理錯誤。(不推薦)
② 在catch中進行處理。(推薦)
先看第一種:
let promise = new Promise(function(resolve,reject){
reject();
});
promise.then(function(){
console.log("resolved");
},function(){
console.log("rejected")
})
輸出 rejected
使用catch
let promise = new Promise(function(resolve,reject){
reject();
});
promise.then(function(){
console.log("resolved");
}).catch(function(){
console.log("catch the reject")
})
輸出 catch the reject
reject的作用就相當於丟擲錯誤,catch或者then的第二個函式引數進行捕獲,再resolve之後再丟擲錯誤是沒有用的,因為狀態一旦發生就無法改變。
Promise物件的錯誤具有冒泡的性質,即所有的錯誤一直可以向後傳遞,知道遇到cantch被捕獲。
注意:一般儘量不要使用then的第二個函式引數進行錯誤處理,儘量使用catch進行錯誤的統一處理。
Promise物件若沒有指定錯誤處理,內部錯誤不會退出程序或終止指令碼執行,也就是說promise物件的內部錯誤不會影響外部程式碼的執行。
let promise = new Promise(function(resolve,reject){
resolve();
});
promise.then(function(){
console.log("resolved");
y+2;
})
setTimeout(function() {
console.log("我出現在y+2之後")
},3000)
上面的程式碼,瀏覽器遇到y+2未宣告丟擲錯誤,但是setTimeout中的字串在3秒後依然可以打印出來。
看一下Promise物件的一些方法:
1. Promise.resolve()
我覺得介紹Promise的方法時應該先介紹這個方法,因為後面要用到。
不要覺得它生成的Promise物件的狀態直接是resolved。(視情況而定)
該方法的作用是將現有物件轉化為一個Promise物件。
將一個物件轉化為Promise物件分為四種情況:
① 引數是Promise的例項,不做任何改變。
② 引數是一個物件,且含有then方法(簡稱thenable物件);
var thenable = {
then:function(resolve,rejected){
resolve(42);
}
}
let p1 = Promise.resolve(thenable);
p1.then(function(data){
console.log(data);//42
})
thanable的then方法執行後,物件p1的狀態就變為resolved,立即執行then。
當然Promise.resolve()也可以生成狀態為rejected的promise物件,上面只需要將resolve改為reject,再在p1.then後面加上.catch用於捕獲錯誤就可以啦!
③ 引數不具有then方法,或者說根本就不是物件的時候。
var p = Promise.resolve(“Hello”);
p.then(function(s){
console.log(s);//Hello
})
返回的promise的例項的狀態直接就是resolved,所以會執行then方法。並且Promise.resolve()的引數會傳遞給回撥函式。
④ 不帶有任何引數,用於快速的生成一個Promise物件。
var p = Promise.resolve();
2. Promise.all()
該方法接受多個Promise例項作為引數,返回一個新的Promise例項。
當所有的Promise例項都返回resolve的時候,新的Promise例項的狀態是fulfilled,此時p1,p2,p3的返回值組成一個數組傳遞給新例項的回撥函式。當有一個返回的是rejecte的時候,新例項的狀態就是rejected。此時第一個返回reject的例項的返回值就會傳遞給p的回撥函式。
promise接受的引數都是promise的例項,那麼怎樣將所有的引數都轉化為promise的例項呢?使用上面的Promise.resolve()方法。
為了更好的掌握Promise.all()方法,來做一個例題。
怎樣使用Promise的相關知識輸出 Welcome To XIAN
var p1 = Promise.resolve("Welcome");
var p2 = "To";
var p3 = new Promise(function(resolve,reject){
setTimeout(function(){
resolve("XIAN");
},1000)
})
其實這個題在我講的這塊出現,大家都知道要使用Promise.all方法,還有問題就是他的引數必須都是Promise的例項,p1已經通過Promise.resolve轉化成了Promise物件,p2我們再使用它轉換一下,p3本身就是Promise物件的例項。
使用下面的程式碼就完美的解決了:
Promise.all([p1,Promise.resolve(p2),p3])
.then(function(data){
console.log(data instanceof Array)//true
console.log(data.join(" "));//轉化為字串輸出
}).catch(function(e){
console.log(new Error(e));
})
上面的data是Promise物件resolve函式的引數組成的陣列。還有all中的順序決定了輸出的順序,與其他的因素沒有關係。
如果所有例項中有catch方法用於捕獲錯誤,則使用Promise.all方法的catch是不會捕獲到的。但是會執行Promise.all中的then方法,為什麼?不是說當所有例項都返回的是resolve狀態時才會觸發Promise.all的then方法麼?
因為當某一個例項報錯時,使用catch進行錯誤處理,返回的是一個新的Promise例項,該例項完成catch之後狀態也會變為resolve,所以導致Promise.all所有例項的都返回resolve,會觸發Promise.all的then。
解決方法就是在例項中不新增catch,那麼例項中reject就會觸發Promise.all的catch,從而達到Promise.all存在的真正意義。
3.Promise.race()
與Promise.all一樣,接受的是promise例項陣列作為引數,新生成一個新的Promise物件。所以該方法的引數可以使用Promise.resolve()方法來解決。
該方法的Promise的物件由第一個返回reject或者resolve的例項的狀態決定。可以使用我們經常看到的是圖片載入超時什麼提示,那麼可以使用該方法實現一下。
載入圖片的函式也使用Promise物件
function preloadImage(path){
return new Promise(function(resolve,reject){
var image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
})
}
要實現的功能是在五秒之後如果圖片載入不出來就提醒圖片載入超時。
Promise.race([preloadImage("/images/1.png"),
new Promise(function(resolve,reject){
setTimeout(function() {
reject();
},5000);
})]).then(function(){
alert("圖片載入成功!")
}).catch(function(){
alert("圖片載入失敗!")
})
在五秒中之內先是圖片載入,(當然圖片載入也有可能失敗)還是在五秒後出發setTimeout函式執行reject。這樣可以允許圖片在五秒之內完成載入給提示。
4.Promise.reject()
看到這個就會想到Promise.resolve().區別就是:
Promise.resolve()生成的Promise物件可以是rejected狀態和resolve狀態。
Promise.reject()只能生成狀態為rejected的Promise例項。
var arg = "Hello";
var pro1 = Promise.reject(arg);
pro1.then(function(){
console.log("resolved");
}).catch(function(e){
console.log(e === arg)//true
console.log(e) //Hello
})
var thenable = {
then(resolve,reject){
reject("出錯了");
}
}
var pro1 = Promise.reject(thenable);
pro1.then(function(){
console.log("resolved");
}).catch(function(e){
console.log(e === thenable) //true
conaloe.log(e);
//{then: ƒ}
//then:ƒ then(resolve,reject)
//__proto__:Object
console.log(e instanceof Object); //true
})
可以看出Promise.reject()方法的引數會原封不動的作為reject的理由,程式設計後續方法的引數。所以這裡輸出的e不是“出錯了”字串,而是傳入Promise.reject()中的引數。它不是像Promise.resolve()一樣使用resolve或者reject中的引數。