Promise的出現,本來是爲了解決回調地獄的問題。全部人在講解Promise
時,都會以一個ajax請求爲例,此處咱們也用一個簡單的ajax的例子來帶你們看一下Promise
是如何使用的。javascript
ajax請求的傳統寫法:java
getData(method, url, successFun, failFun){ var xmlHttp = new XMLHttpRequest(); xmlHttp.open(method, url); xmlHttp.send(); xmlHttp.onload = function () { if (this.status == 200 ) { successFun(this.response); } else { failFun(this.statusText); } }; xmlHttp.onerror = function () { failFun(this.statusText); }; }
改成promise
後的寫法:ajax
getData(method, url){ var promise = new Promise(function(resolve, reject){ var xmlHttp = new XMLHttpRequest(); xmlHttp.open(method, url); xmlHttp.send(); xmlHttp.onload = function () { if (this.status == 200 ) { resolve(this.response); } else { reject(this.statusText); } }; xmlHttp.onerror = function () { reject(this.statusText); }; }) return promise; } getData('get','www.xxx.com').then(successFun, failFun)
很顯然,咱們把異步中使用回調函數的場景改成了.then()
、.catch()
等函數鏈式調用的方式。基於promise
咱們能夠把複雜的異步回調處理方式進行模塊化。數組
下面,咱們就來介紹一下Promise
究竟是個什麼東西?它是如何作到的?promise
Promise
的原理分析其實promise
原理提及來並不難,它內部有三個狀態,分別是pending
,fulfilled
和rejected
。異步
pending
是對象建立後的初始狀態,當對象fulfill
(成功)時變爲fulfilled
,當對象reject
(失敗)時變爲rejected
。且只能從pengding
變爲fulfilled
或rejected
,而不能逆向或從fulfilled
變爲rejected
、從rejected
變爲fulfilled
。如圖所示:模塊化
Promise
實例方法介紹Promise
對象擁有兩個實例方法then()
和catch()
。函數
從前面的例子中能夠看到,成功和失敗的回調函數咱們是經過then()
添加,在promise
狀態改變時分別調用。promise
構造函數中一般都是異步的,因此then
方法每每都先於resolve
和reject
方法執行。因此promise
內部須要有一個存儲fulfill
時調用函數的數組和一個存儲reject
時調用函數的數組。oop
從上面的例子中咱們還能夠看到then
方法能夠接收兩個參數,且一般都是函數(非函數時如何處理下一篇文章中會詳細介紹)。第一個參數會添加到fulfill
時調用的數組中,第二個參數添加到reject
時調用的數組中。當promise
狀態fulfill
時,會把resolve(value)
中的value
值傳給調用的函數中,同理,當promise
狀態reject
時,會把reject(reason)
中的reason
值傳給調用的函數。例:ui
var p = new Promise(function(resolve, reject){ resolve(5) }).then(function(value){ console.log(value) //5 }) var p1 = new Promise(function(resolve, reject){ reject(new Error('錯誤')) }).then(function(value){ console.log(value) }, function(reason){ console.log(reason) //Error: 錯誤(…) })
then
方法會返回一個新的promise
,下面的例子中p == p1
將返回false
,說明p1
是一個全新的對象。
var p = new Promise(function(resolve, reject){ resolve(5) }) var p1 = p.then(function(value){ console.log(value) }) p == p1 // false
這也是爲何then
是能夠鏈式調用的,它是在新的對象上添加成功或失敗的回調,這與jQuery
中的鏈式調用不一樣。
那麼新對象的狀態是基於什麼改變的呢?是否是說若是p
的狀態fulfill
,後面的then
建立的新對象都會成功;或者說若是p
的狀態reject
,後面的then
建立的新對象都會失敗?
var p = new Promise(function(resolve, reject){ resolve(5) }) var p1 = p.then(function(value){ console.log(value) // 5 }).then(function(value){ console.log('fulfill ' + value) // fulfill undefined }, function(reason){ console.log('reject ' + reason) })
上面的例子會打印出5和"fulfill undefined"說明它的狀態變爲成功。那若是咱們在p1
的then
方法中拋出異常呢?
var p = new Promise(function(resolve, reject){ resolve(5) }) var p1 = p.then(function(value){ console.log(value) // 5 throw new Error('test') }).then(function(value){ console.log('fulfill ' + value) }, function(reason){ console.log('reject ' + reason) // reject Error: test })
理所固然,新對象確定會失敗。
反過來若是p
失敗了,會是什麼樣的呢?
var p = new Promise(function(resolve, reject){ reject(5) }) var p1 = p.then(undefined, function(value){ console.log(value) // 5 }).then(function(value){ console.log('fulfill ' + value) // fulfill undefined }, function(reason){ console.log('reject ' + reason) })
說明新對象狀態不會受到前一個對象狀態的影響。
再來看以下代碼:
var p = new Promise(function(resolve, reject){ reject(5) }) var p1 = p.then(function(value){ console.log(value) }) var p2 = p1.then(function(value){ console.log('fulfill ' + value) }, function(reason){ console.log('reject ' + reason) // reject 5 })
咱們發現p1
的狀態變爲rejected
,從而觸發了then
方法第二個參數的函數。這彷佛與咱們以前提到的有差別啊,p1
的狀態受到了p
的狀態的影響。
再來看一個例子:
var p = new Promise(function(resolve, reject){ resolve(5) }) var p1 = p.then(undefined, function(value){ console.log(value) }) var p2 = p1.then(function(value){ console.log('fulfill ' + value) // fulfill 5 }, function(reason){ console.log('reject ' + reason) })
細心的人可能會發現,該例子中then
第一個參數是undefined
,且value
值5被傳到了p1
成功時的回調函數中。上面那個例子中then
的第二個參數是undefined
,一樣reason
值也傳到了p1
失敗時的回調函數中。這是因當對應的參數不爲函數時,會將前一promise
的狀態和值傳遞下去。
promise
含有一個實例方法catch
,從名字上咱們就看得出來,它和異常有千絲萬縷的關係。其實catch(onReject)
方法等價於then(undefined, onReject)
,也就是說以下兩種狀況是等效的。
new Promise(function(resolve, reject){ reject(new Error('error')) }).then(undefined, function(reason){ console.log(reason) // Error: error(…) }) new Promise(function(resolve, reject){ reject(new Error('error')) }).catch(function(reason){ console.log(reason) // Error: error(…) })
咱們提到參數不爲函數時會把值和狀態傳遞下去。因此咱們能夠在多個then
以後添加一個catch
方法,這樣前面只要reject
或拋出異常,都會被最後的catch
方法處理。
new Promise(function(resolve, reject){ resolve(5) }).then(function(value){ taskA() }).then(function(value){ taskB() }).then(function(value){ taskC() }).catch(function(reason){ console.log(reason) })
Promise
的靜態方法Promise
還有四個靜態方法,分別是resolve
、reject
、all
、race
,下面咱們一一介紹。
除了經過new Promise()
的方式,咱們還有兩種建立Promise
對象的方法:
Promise.resolve()
它至關於建立了一個當即resolve
的對象。以下兩段代碼做用相同:
Promise.resolve(5) new Promise(function(resolve){ resolve(5) })
它使得promise對象直接resolve
,並把5傳到後面then
添加的成功函數中。
Promise.resolve(5).then(function(value){ console.log(value) // 5 })
Promise.reject()
很明顯它至關於建立了一個當即reject
的對象。以下兩段代碼做用相同:
Promise.reject(new Error('error')) new Promise(function(resolve, reject){ reject(new Error('error')) })
它使得promise對象直接reject
,並把error傳到後面catch
添加的函數中。
Promise.reject(new Error('error')).catch(function(reason){ console.log(reason) // Error: error(…) })
Promise.all()
它接收一個promise對象組成的數組做爲參數,並返回一個新的promise
對象。
當數組中全部的對象都resolve
時,新對象狀態變爲fulfilled
,全部對象的resolve
的value
依次添加組成一個新的數組,並以新的數組做爲新對象resolve
的value
,例:
Promise.all([Promise.resolve(5), Promise.resolve(6), Promise.resolve(7)]).then(function(value){ console.log('fulfill', value) // fulfill [5, 6, 7] }, function(reason){ console.log('reject',reason) })
當數組中有一個對象reject
時,新對象狀態變爲rejected
,並以當前對象reject
的reason
做爲新對象reject
的reason
。
Promise.all([Promise.resolve(5), Promise.reject(new Error('error')), Promise.resolve(7), Promise.reject(new Error('other error')) ]).then(function(value){ console.log('fulfill', value) }, function(reason){ console.log('reject', reason) // reject Error: error(…) })
那當數組中,傳入了非promise對象會如何呢?
Promise.all([Promise.resolve(5), 6, true, 'test', undefined, null, {a:1}, function(){}, Promise.resolve(7) ]).then(function(value){ console.log('fulfill', value) // fulfill [5, 6, true, "test", undefined, null, Object, function, 7] }, function(reason){ console.log('reject', reason) })
咱們發現,當傳入的值爲數字、boolean、字符串、undefined、null、{a:1}、function(){}等非promise對象時,會依次把它們添加到新對象resolve
時傳遞的數組中。
那數組中的多個對象是同時調用,仍是一個接一個的依次調用呢?咱們再看個例子
function timeout(time) { return new Promise(function (resolve) { setTimeout(function () { resolve(time); }, time); }); } console.time('promise') Promise.all([ timeout(10), timeout(60), timeout(100) ]).then(function (values) { console.log(values); [10, 60, 100] console.timeEnd('promise'); // 107ms });
由此咱們能夠看出,傳入的多個對象幾乎是同時執行的,由於總的時間略大於用時最長的一個對象resolve
的時間。
Promise.race()
它一樣接收一個promise對象組成的數組做爲參數,並返回一個新的promise
對象。
與Promise.all()
不一樣,它是在數組中有一個對象(最先改變狀態)resolve
或reject
時,就改變自身的狀態,並執行響應的回調。
Promise.race([Promise.resolve(5), Promise.reject(new Error('error')), Promise.resolve(7)]).then(function(value){ console.log('fulfill', value) // fulfill 5 }, function(reason){ console.log('reject',reason) }) Promise.race([Promise.reject(new Error('error')), Promise.resolve(7)]).then(function(value){ console.log('fulfill', value) }, function(reason){ console.log('reject',reason) //reject Error: error(…) })
且當數組中有非異步Promise
對象或有數字、boolean、字符串、undefined、null、{a:1}、function(){}等非Promise
對象時,都會直接以該值resolve
。
Promise.race([new Promise((resolve)=>{ setTimeout(()=>{ resolve(1) },100)}), Promise.resolve(5), "test", Promise.reject(new Error('error')), Promise.resolve(7)]).then(function(value){ console.log('fulfill', value) // fulfill 5 }, function(reason){ console.log('reject',reason) }) // fulfill 5
數組中第一個元素是異步的Promise
,第二個是非異步Promise
,會當即改變狀態,因此新對象會當即改變狀態並把5
傳遞給成功時的回調函數。
那麼問題又來了,既然數組中第一個元素成功或失敗就會改變新對象的狀態,那數組中後面的對象是否會執行呢?
function timeout(time) { return new Promise(function (resolve) { setTimeout(function () { console.log(time) resolve(time); }, time); }); } console.time('promise') Promise.race([ timeout(10), timeout(60), timeout(100) ]).then(function (values) { console.log(values); [10, 60, 100] console.timeEnd('promise'); // 107ms }); // 結果依次爲 // 10 // 10 // promise: 11.1ms // 60 // 100
說明即便新對象的狀態改變,數組中後面的promise對象還會執行完畢,其實Promise.all()
中即便前面reject
了,全部的對象也都會執行完畢。規範中,promise對象執行是不能夠中斷的。
promise
對象即便立馬改變狀態,它也是異步執行的。以下所示:
Promise.resolve(5).then(function(value){ console.log('後打出來', value) }); console.log('先打出來') // 結果依次爲 // 先打出來 // 後打出來 5
但還有一個有意思的例子,以下:
setTimeout(function(){console.log(4)},0); new Promise(function(resolve){ console.log(1) for( var i=0 ; i<10000 ; i++ ){ i==9999 && resolve() } console.log(2) }).then(function(){ console.log(5) }); console.log(3);
結果是 1 2 3 5 4,命名4是先添加到異步隊列中的,爲何結果不是1 2 3 4 5呢?這個涉及到Event loop,後面我會單獨講一下。