對於一些還不具有大量編程經驗的朋友來講,promise多是es6比較難以掌握的點。首先是不少名詞,好比Promises,es6 Promise, 回調函數(callback),Promise/A+,異步編程等。下面就首先介紹下這些名詞的含義和區別。es6
所謂異步編程中的異步是相對於同步的概念的。js是單線程的語言,同一時間只能作一件事,爲了指定一些稍後要執行的代碼,咱們須要異步。在客戶端,主要的異步方式有事件,setTimeout,Ajax等。Node的發展大大擴展了js語言的邊界,咱們知道,Node使用非阻塞IO模型,它使用回調函數模式來實現異步編程。好比:編程
readFile('example.txt', function(err, contents){ if(err){ throw err; } console.log(contents); }); console.log('Hi!');
上面代碼中readFile的第二個參數就是回調函數。它會在讀取完example.txt後被添加到執行隊列中。上面代碼的執行順序是--執行readFile函數,在遇到讀取文件時暫停,打印"Hi",讀取文件結束後將回調添加到做業隊列中,執行回調函數,打印contents。segmentfault
原本呢,使用回調函數是可以完成異步編程的。可是隨着代碼的邏輯越複雜,這種異步編程方式愈來愈難以閱讀和追蹤程序錯誤,因此發展出了Promises規範來完成異步編程。數組
Promises是一系列異步編程規範的統稱。咱們須要瞭解的是其中的Promise/A+規範。es6經過Promise這個內建對象實現了該規範。因此咱們可使用es6中的Promise對象來進行異步編程。promise
下面將對es6中的Promise對象進行介紹。至於jQuery中延遲對象$.deferred(),根據規範本身實現promise和ES7的Async/Await異步方式等更多內容,後面會專門寫一篇文章進行介紹。異步
一個promise實例有3種狀態,分別是:異步編程
promise處於這3種狀態中的一種,而且能夠由pending狀態變爲fulfilled狀態,或由pending變爲rejected狀態。反之則不行。函數
爲了便於理解,下面將經過一個生活化的例子,來解釋什麼是Promise?學習
Promise是允諾的意思。它就是一個關於未發生的事情的承諾。好比:this
你訂了一份燒烤,店家說半個小時內送到,這就是一個Promise。如今,這個Promise尚未發生,因此可能半個小時內配送成功或者失敗。對此,你預備了兩種處理方式:成功 -- 美滋滋的吃燒烤,失敗 -- 去樓下店裏吃。
在半個小時內,這個Promise處於pending狀態,你正常上網,擼代碼。一段時間後,這個promise就有告終果。是成功(fulfilled)或者失敗(rejected)。根據這個結果,你以前的兩種處理方式就會相應執行。這就是promise。
對應的代碼以下:
let promise = new Promise(function(resolve, reject){ //等待店家送來中... let result = '配送成功'? true : false; if(result){ resolve(value); }else{ reject(reason); } }); promise.then(function(value){ //美滋滋吃燒烤... //value爲上面resolve()中傳遞的值, 好比共100塊錢。 }, function(reason){ //叫上隔壁老王去樓下吃... //reason爲上面reject()的傳遞的緣由,好比烤糊了... });
上面代碼就是經過promise異步編程的代碼。這裏要注意的是Promise構造函數接收一個函數做爲參數,函數內部是異步的邏輯。這個函數接收兩個參數:resolve和reject。resolve()能夠把promise推向fulfilled狀態,reject()能夠把promise推向rejected狀態。
promise有個then方法,用於處理promise成功或失敗後的邏輯。then有兩個參數:
參數1爲promise成功時執行的函數,該函數的參數value對應於上面resolve(value)中的value值;
參數2爲promise失敗時執行的函數,該函數的參數reason對應於reject(reason)中的reason值,表示失敗的緣由。
一旦promise有告終果(成功或失敗),就會執行對應then中的函數。
Ajax是客戶端最經常使用的異步編程場景,下面一個例子演示了使用Promise進行Ajax操做的代碼。
function getData(method, url){ let promise = new Promise(function(resolve, reject){ let 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); function successFun(value){ //Ajax成功處理函數... } function failFun(reason){ //Ajax失敗處理函數... }
前面的例子promise建立時,promise都處於pending狀態,根據異步操做的結果將promise推向成功或失敗狀態。
Promise類型有兩個靜態方法Promise.resolve(value),Promise.reject(reason)能夠分別建立已是fulfilled和已是rejected狀態的promise。
好比:
let promise = Promise.resolve(44); promise.then(function(value){ console.log('fulfilled', value); })
上面代碼promise在被建立出來時,已是fulfilled狀態,接下來會直接將then中的回調函數加入到做業隊列中,等待做業隊列中前面的任務完成後執行該函數。
這裏傳入Promise.resolve(value)和Promise.reject(reason)中的參數和以前Promise構造是對應的參數是同樣的。
上面已經演示過promise實例上then方法的用法,每個promise實例還具備catch方法。
catch()方法只處理reject的狀況,他的行爲與調用Promise.prototype.then(undefined, onRejected)相同。好比:
let p = new Promise(function(resolve, reject){ //... reject(new Error('something wrong!')) }) p.catch(function(reason){ //拒絕 })
上面catch方法中的回調在promise被reject時調用。
每次對then()或catch()的調用都會返回另外一個promise,這也是不少代碼能夠寫成相似鏈式調用的緣由。好比:
let p1 = new Promise(function(resolve, reject){ resolve(42); }); let p2 = p1.then(function(value){ console.log(value); }) p2.then(function(){ console.log("Finished"); }, function(){ console.log('something wrong!'); }); p1 == p2 // false,注意:p1.then()會返回一個新的promise,因此p1與p2並不相等 //能夠寫成鏈式調用的形式,好比 p1.then(function(value){ console.log(value) }).then(function(){ console.log('do something'); }).then(function(){ console.log('Finished'); })
在上面代碼中,p1.then()返回了一個promise爲p2, 那麼p2的狀態和p1之間有什麼關係呢?
更具體一點說,當p1變爲fulfilled時,p1.then()返回的p2是什麼狀態呢?兩者有什麼聯繫呢?
p2的行爲與p1.then()中回調函數的返回值有關:
Promise內建對象上的靜態方法Promise.all()用於處理多個promise的狀況。
Promise.all([promise1, promise2,...])返回一個promise的實例,接收一個promise組成的數組爲參數。只有當數組內的promise都成功時,纔會調用對應的then中的成功處理函數,只要有一個不成功,那麼調用對應的拒絕處理函數。
依然使用前面那麼訂燒烤的例子,你不只訂了燒烤,還在另外一家訂了啤酒。打算等到燒烤和啤酒都配送成功後一塊兒吃,美滋滋~~。好比:
Promise.all([訂燒烤,訂啤酒]).then(function(value){ //吃燒烤,喝啤酒... }, function(reason){ //拒絕的緣由,烤糊了或者啤酒賣完了... })
這裏要注意的一點是,對於數組中的promise,只要有任一個promise爲拒絕,那麼就會當即執行then中的拒絕處理函數,並不會等待其餘promise的結果。只有當全部promise的結果都成功時,才執行then中的成功處理函數。好比:
var p1 = new Promise(function(resolve, reject){ setTimeout(function(){ console.log('A'); resolve(); }, 1000) }); var p2 = Promise.reject(new Error('error')); var p3 = new Promise(function(resolve, reject){ setTimeout(function(){ console.log('B'); resolve(); }, 0) }); Promise.all([p1,p2,p3]).then(function(value){ console.log('success!'); }, function(reason){ console.log('failed'); }) //結果爲failed B A
因爲p2爲已拒絕狀態的promise,因此Promise.all()當即變爲拒絕狀態,打印failed,p1和p2會繼續執行,但對於Promise.all()的結果沒有影響。
Promise內建對象上的靜態方法Promise.race()一樣用於處理多個promise的狀況。一樣返回一個Promise,一樣接收一個promise數組做爲參數。
與all不一樣的地方在於,數組中的promise就像在賽跑同樣(race),而且只關心第一名的狀況,只要有其中一個promise有告終果,Promise.race()的狀態就會當即與該promise相同。
數組中其餘promise繼續執行,但對於Promise.race()的結果沒有影響。
var promise = new Promise(function(resolve, reject){ console.log('A'); resolve('C'); }) console.log('B'); setTimeout(function(){ console.log('D'); },0) promise.then(function(value){ console.log(value) }); //打印A, B, C, D
p.then(function(value){ //... }) //或者 p.then(undefined, function(reason){ //... })
那麼,相應的回調處理函數被忽略,then方法返回的promise會保留上一個promise的狀態和參數。最典型的例子:
var p = new Promise(function(resolve, reject){ reject(new Error('error')); }) p.then(function(value){ //... }).then(function(value){ //... }).then(undefined, function(reason){ console.log(reason); }) //打印'error'
p的前兩次then調用的拒絕處理函數被忽略,而後reject狀態和錯誤信息就一直日後傳遞,直到被最後一次then調用捕獲。
關於Promise的知識點不少,可是最經常使用的場景就是Ajax。好比:
function getData(method, url){ var promise = new Promise(function(resolve, reject){ //Ajax獲取數據的代碼... if(success){ resolve(response) }else{ reject(statusText) } }) return promise; } getData('get','www.xxx.com').then(Fun1).then(Fun2).then(Fun3).catch(function(reason){ //錯誤處理邏輯... });
更多關於es6的內容,能夠關注右側個人專欄--學習ES6。
參考:
MDN Javascript Promise.
Promise介紹-基礎篇.《深刻理解ES6》-- Promise與異步編程。