Promise
是ES6的特性之一,採用的是 Promise/A++
規範,它抽象了異步處理的模式,是一個在JavaScript中實現異步執行的對象。
按照字面釋意 Promise
具備「承諾」的含義,它承諾當異步處理完成後,回饋一個結果給你!或者你能夠將其認爲是一個狀態機,一旦狀態發生了改變,便會觸發對應的行爲。編程
Promise
最先出現於E語言中(一種給予並列/並行處理設計的編程語言),JavaScript 引入這一特性,旨在爲了規範異步的操做和避免陷入回調地獄。數組
目錄promise
Promise
的使用主要有兩種方式,一種是對象實例化操做,它具備固定的使用格式:dom
new Promise(exector);
具體示例:異步
var promise = new Promise(function(resolve,reject){ if(success){ resolve(); }else{ reject(); } })
exector
是一個做爲參數的匿名函數,它接收兩個參數,一個是 resolve
,另外一個則是 reject
,這兩個參數都是方法,經過執行兩個方法咱們能夠修改 Promise
實例對象的狀態,使其再觸發對應的行爲。編程語言
另外一種則是靜態調用,這些方法自己就是 Promise
對象的靜態實現:函數
Promise.resolve().then(resolve); Promise.reject().then(undefined,rejected); Promise.reject().catch(rejected);
靜態調用經常使用於快速執行一個異步操做,例如在咱們的程序功能中有一個耗時很長的循環,這個循環的目的只是爲了計算一個結果並顯示,可是若直接放在程序的上下文的地方,會致使阻塞,經常使用的方式是將其加入到一個定時器中進行異步操做:工具
setTimeout(function(){ for(;;){;} },16);
可是學習了靜態調用 Promise
,咱們徹底能夠將這個操做放入到要給 Promise
的異步回調中。oop
Promise.resolve().then(function(){ for (var i = 0; i < 100000; i++) { if(i === 100000/2){ console.log('loop end2'); } } });
對比這兩種方式,不難發現經過對象實例的方式,咱們能夠爲實例對象賦予更多的功能,能夠根據自身的須要手動的改變 Promise
的狀態,而使用靜態調用的方式,則能夠快速的進行異步操做。
須要注意的是,不管是靜態的方式仍是實例化對象,根據Promise的狀態被調用的方法都是以異步方式執行的。可是 Promise
對象實例化的過程卻依然是同步的。學習
Promise
有三種狀態:pendding
、rejected
、fulfilled
;
pendding
: 表示初始化狀態rejected
: 表示失敗狀態fulfilled
: 表示成功完成狀態而狀態的變化,則須要經過執行對應的方法來完成,
resolve()
方法來完成。reject()
方法來完成。Promise
默認的狀態是 pendding
狀態,這種狀態出如今實例對象剛剛初始化的狀況,結束於 resolve()
或者是 reject()
方法調用以後。一旦狀態發生改變,便沒法再修改,也所以說明,狀態改變後執行的回調操做 then
也只會執行一次。除此以外,在 Promise
中主動使用 throw new Error()
也可使 promise的狀態改變爲 rejected
。
Promise
的實例對象一旦建立好後,會大體具備如下的操做:
pendding
。then
、catch
等異步處理方法exector
方法(同步的方式),根據具體的行爲來決定是否變動 promise實例對象的狀態。實際上經過靜態調用的方式來執行 Promsie,除了不具備 exector
其它的都是相同的。
須要着重說明的是 then
與 catch
這兩個方法,它們都是 Promise
對象的狀態回調函數,一旦 promise的狀態發生改變,便會對應的進行觸發。
var promise = new Promise(function(resolve, reject) { var num = Math.random() * 5; setTimeout(function() { if (num >= 2.5) { reject('數值過大'); } else { resolve(num); } }, 1000) }); promise.then(function(v) { console.log('success:' + v); }, function(v) { console.log(v) });
then
方法有兩個參數:promise.then(onResolved,onRejected)
,其中 onResolved
表示成功(狀態變動爲 fulfilled)的回調函數,而 onRejected
則表示失敗(狀態變動爲 rejected)狀況下的回調函數,通常來講第二個參數能夠忽略不寫,只保留成功的回調方法 then(onResolved)
,可是若是你只想處理失敗的回調函數,那麼 onResolved
並不能被省去,promise.then(undefined,onRejected)
。
或者將 rejected
的處理單獨提取出來是更好的辦法:
promise.then(function(v) { console.log('success:' + v); }).catch(function(v) { console.log(v) });
Promsie 支持這種相似JQ
的鏈式調用,而且能夠同時連續調用多個 then
方法,而這裏的 catch
方法與咱們的 try..catch
功能相同,都是用於捕獲錯誤。並且還能夠將錯誤單獨的提取出來,這便爲咱們帶來一個很是大的優點那就是哪怕我then
方法中的 onResolved
方法執行錯誤,也不會阻塞其它代碼的執行。
而可以以鏈式連續屢次執行 then
方法的緣由就在於咱們調用 then
方法的時候,該方法會返回一個新的 promise 對象,一樣的,對於 catch
方法而言道理也是相同的,只是 catch
不能作到屢次調用。
經過這個簡單的實例,咱們可知一個promise實例對象的執行是同步的,可是根據狀態改變的句柄方法是異步執行的,同時這些句柄方法還會再次返回一個新的 Promise
對象,用於進行鏈式調用。
Promsie
實例對象中的 then
或者是 catch
方法不只能夠接受 exector
中經過 resolve(value)
或者是 reject(value)
傳遞而來的值,還能夠在其回調函數中經過 return
語句將值傳遞給調用鏈的下一個 then
或者是 catch
方法。
Promise.resolve(1).then(function(v){return v+1}).then(function(v){return v+1}).then(function(v){console.log(v)}) // 3
實際上當 then
方法執行完成後,會返回一個新的 Promise
對象,而且將本身接收的值附加到這個promise對象上做爲一個參數值,供調用鏈上的下一個 then
或者是catch
方法讀取並處理。
若是去詳細的討論 resolve(value)
或者是 reject(value)
值的傳輸的話,它主要有如下幾種狀況:
Promise.resolve(Promise.resolve(3)).then(function(v){console.log(v)}); Promise.resolve('123').then(function(v){console.log(v)})
resolve
與 reject
基本相同,不一樣的只是,若是reject接收到的是另外一個promise做爲參數,則返回的並非新的promise對象,依然是其自己。
與ES3中咱們會用 try..catch..finally
來進行異常的處理,那麼在Promise中,異常都會有何種的流程呢?
Promise.reject(1).then(function(v) { console.log('success:' + v); return v }).catch(function(v) { console.log('error:' + v); return v }).then(function(v) { console.log(v); return v; }).then(function(v) { console.log(v) }); /* * error:1 * 1 * 1 * /
從運算的結果上咱們能夠看出,then..catch..then
的結構就相似於ES3中的 try...catch..finally
;
再看下面的示例,
Promise.reject(1).then(function(v) { console.log('success:' + v); return v }).catch(function(v) { console.log('error:' + v); return v }).then(function(v) { console.log(v); return v; }).catch(function(v) { console.log('error2:'+v); return v; }).then(function(v){ console.log(v) }); /* * error:1 * 1 * 1 * /
從這個實例中咱們就能夠得知多個catch
只會有一個會被觸發,而且是最先的那個。
Promise.all
能夠執行一個由衆多 promise對象組成的數組,並返回一個新的 promise對象,新返回的 promise對象其狀態會根據所執行的 promise對象數組的狀態而定,若是數組中的全部promise對象都是resolved狀態,Promise.all
返回的 promise對象纔會觸發 resolved狀態,不然中止 Promise.all
的執行,並返回一個rejected 狀態的promsie對象。
var p1 = Promise.resolve(1); var p2 = Promise.resolve(2); var p3 = Promise.resolve(3); Promise.all([ p1, p2, p3 ]).then(function(vs){ console.log(vs) })
Promise.all
這種以執行最慢的那個異步爲準的特性,可使用它來作網頁資源的預加載。
與 Promise.all
相同,race也能夠執行衆多promise對象組成的數組,只是不一樣的是,只要在這個數組有一個Promise狀態發生了改變,(resolved或者是rejected)就會使race返回一個新的 promise對象,並且這個對象的狀態也是基於 promise數組執行時的狀態。
var p1 = Promise.resolve(1); var p2 = Promise.resolve(2); var p3 = Promise.resolve(3); Promise.race([ p1, p2, p3 ]).then(function(vs){ console.log(vs) })
若是說 Promise.all
會以Promise數組中最慢的爲準,那麼 race 則會以數組中最早執行的那個Promise對象爲基準,所以利用這一個特性,能夠用 race來設置超時時間。
var p1 = new Promise(function(resolve, reject) { setTimeout(function() { reject('超時') }, 5000) setTimeout(function() { resolve('success') }, 10000) }); var p2 = new Promise(function(resolve, reject) { setTimeout(function() { resolve('success') }, 6000) }); var p3 = new Promise(function(resolve, reject) { setTimeout(function() { resolve('success') }, 6000) }); Promise.race([p1, p2, p3]);
Promise
自己是一個異步對象,當異步對象狀態更改時觸發對應狀態的handle方法。所以若是想讓多個promise對象同步執行,必須將具備依賴關係的 promise對象放置到對應的另外一個promise對象的回調中。
function getAsync(v){ return new Promise(function(resolve,reject){ resolve(v); }); } function main(){ return getAsync(1000).then(pushValue).then(function(){return getAsync(500).then(pushValue)}).then(function(){return getAsync(1500).then(pushValue)}) } main().then(function(){ console.log('所有執行完成!'); })
Promise的本質是維護狀態,偵測狀態,根據狀態進行響應。而callback
則是將自身做爲參數傳入到另外一個方法中,做爲別的方法體的一部分去調用,所以 Promsie要比 Callback靈活的不少。
若是用代碼來作對比的話,promise是這樣的:
Promise.resove(2).then(function(v){return v}).then(function(v){return v})
而 callback的方式則是這樣的:
doAsync1(function () { doAsync2(function () { doAsync3(function () { doAsync4(function () { }) }) })
總的來講,Promsie的優點更體現與鏈式調用,而callback(相比較Promise的劣勢)就體如今嵌套使用
對於 Promsie
的實現,Jquery
有着本身的一套實現方式,那就是JQ的 $.Deferred
方法。
經過調用 $.Deferred
咱們能夠得到一個JQ版的異步對象實例。
簡單實例:
var def = $.Deferred(); //得到一個JQ的異步對象實例。 def.resolve('success').then(function(v){console.log(v)}); // success
是否是與ES6的 promise
徹底同樣?若是你真的這樣認爲那就錯了,繼續看下面的示例:
var def = $.Deferred(); def.then(function(v){console.log(v)}); def.resolve('success');
是否是發現了一個很大的不一樣之處,異步對像實例 def
居然能夠經過resolve()
方法本身修改本身的狀態!而ES6中的Promsie
標準規定的是異步對象的狀態不能手動改變,雖然二者不一樣,可是也無需大驚小怪,畢竟JQ的 $.Deferred
有着本身的實現標準。並且,JQ也提供了另外一種受限的異步對象,這個受限的異步對象,基本上就與ES6的 Promise
基本一致了。
可是這個受限的異步對象必需要配合必定的寫法,才能避免被手動更改狀態。
function getAsync() { var def = $.Deferred(); setTimeout(function() { def.resolve('success'); }, 1000); return def.promise(); //返回一個受限制的異步對象實例。 } var dep = getAsync(); dep.then(function(v) { console.log(v); return v }).then(function(v) { console.log(v); return v });
咱們能夠經過比較受限與不受限的兩種異步對象實例,從而更直觀的瞭解這這二者的區別:
console.log($.Deferred()); console.log($.Deferred().promise());
經過打印這兩種異步對象,咱們明顯能夠看到受限的對象其含有的方法要遠遠少於沒有受限的實例對象,而其中最明顯的就是受限的實例對象並不具備 resolve
方法,這也就直接的說明了受限的異步對象是沒法直接修改本身的狀態。
因爲使用最多的仍是受限的異步對象,因此這裏咱們就大體的說下受限的異步對象具備的一些方法。
then
JQ中異步對象實例的 then
方法與ES6的Promise
對象實例的 then
方法使用格式與功能基本相同,惟一不一樣的就是JQ對 then
方法的回調處理進行了擴展,加入了 pedding
狀態時的回調。
function getAsync() { var def = $.Deferred(); def.notify('loading'); //指定pedding時觸發回調,並傳入參數。 setTimeout(function() { def.resolve('success'); }, 1000); return def.promise(); //返回一個受限制的異步對象實例。 } var dep = getAsync(); dep.then(function(v) { console.log(v) }, function() {}, function(v) { console.log(v) });
done/fail/progress
done()
、fail()
、progress()
等方法都是對 then()
方法的功能包裝。
done()
表示resolved
狀態時的處理方法,fail()
表示 rejected
狀態時的處理方法,progress()
表示pendding
狀態時的處理方法。
function getAsync() { var def = $.Deferred(); def.notify('loading') setTimeout(function() { def.reject('fail'); }, 1000); return def.promise(); //返回一個受限制的異步對象實例。 } var dep = getAsync(); dep.done(function(v){ console.log(v); }); dep.fail(function(v){ console.log(v); }); dep.progress(function(v){ console.log(v); })
always
經過JQ Deferred().promise()
方法返回的受限的異步對象實例中,always
方法相似於 try..catch..finally
中的 finally
,不論異步對象的狀態是成功仍是失敗,都會觸發該方法。
function getAsync() { var def = $.Deferred(); setTimeout(function() { def.resolve('success'); }, 1000); return def.promise(); //返回一個受限制的異步對象實例。 } var dep = getAsync(); dep.always(function(v){console.log(v)});
state
經過調用 state()
方法能夠得到當前對象實例的狀態。
$.Deferred().promise().state(); //pending
學習一門技術或者是一個工具,最好的辦法,莫非於瞭解它們的大體實現,這裏我以本身的方式,編寫一個簡單的 Promise
對象。
如今只是一個簡單的示例,功能還很是簡陋只有 then、resolve、reject
等功能,並且還有不少bug,可是也足夠讓我對 promise
有更進一步的認識。
function Deferred(fn) { var _this = this; var doneList = []; // 用於保存 then方法中 resolved狀態時的回調函數. var failCallbck = []; // 用於保存 then方法中 rejected狀態時的回調函數. this.PromiseValue = undefined; //promsie的值 this.PromiseStatus = 'pending'; //promise的狀態 function resolve(v) { //resolve狀態的執行函數 setTimeout(function() { //脫離同步代碼,以異步的方式執行 then 方法中的代碼。 _this.PromiseStatus = 'resolved'; _this.PromiseValue = v; doneList.forEach(function(self, index) { _this.PromiseValue = self(_this.PromiseValue); //保存then方法中回調的return值,以供鏈式調用時下個then回調函數使用。 }); }, 0); } function reject(v) { //reject狀態的執行函數 var idx; //定位failCallbck中最後的失敗處理函數的索引。 eg: [1,...,n] 1表示最先最後,n表示最早最近的 setTimeout(function() { //脫離同步代碼,以異步的方式執行 then 方法中的代碼。 _this.PromiseStatus = 'rejected'; _this.PromiseValue = v; failCallbck.forEach(function(f, i) { if (typeof f != 'undefined' && typeof f == 'function') { idx = i; _this.PromiseValue = f(_this.PromiseValue); return; //對於異常處理函數,只會執行最後的那一個。 } }); _this.PromiseStatus = 'resolved'; //遍歷執行doneList中 resolved 狀態時的回調函數,可是忽略rejected處理函數以前的全部resolve 狀態的回調函數 for (var i = idx + 1; i < doneList.length; i++) { _this.PromiseValue = doneList[i](_this.PromiseValue); } }, 0) } this.then = function(done, faill) { if (this.PromiseStatus === 'pending') { doneList.push(done); failCallbck.push(faill); } else if (this.PromiseStatus === 'resolved') { done(); } else { failCallbck = faill; } return this; } try { fn(resolve, reject); } catch (e) { throw new Error(e); } }
調用:
var def1 = new Deferred(function(resolve, reject) { resolve(1); }); var def2 = new Deferred(function(resolve, reject) { reject(2); }); def1.then(function(e) { console.log('success:' + e); return e + 1; }, function(e) { console.log('error:' + e); return e + 1; }).then(function(v) { console.log(v); }); def2.then(function(e) { console.log('success:' + e); return e + 1; }, function(e) { console.log('error:' + e); return e + 1; }).then(function(v) { console.log(v); });
var p = new Promise(function(resolve,reject){ console.log(1); resolve(2); console.log(4) }); p.then(function(){ console.log(3); }) console.log(5)
在實例化對象的時候,代碼的執行依然是同步執行,而實例化對象的狀態回調函數 then,catch
纔是異步執行。
var p = new Promise(function(resolve,reject){ resolve('success1'); reject('error1'); resolve('success2'); }); p.then(function(e){console.log(e)}).catch(function(v){console.log(v)})
Promise
的狀態一旦肯定將沒法改變。
Promise.resolve(1).then(function(v) { console.log(v); return new Error('error!!!'); }).then(function(v) { console.log(v); }).catch(function(e) { console.log(e); });
resolve()
進入了第一個then
的回調,雖然返回了一個 error
類型,可是是經過 return
返回的,因此它將會被做爲第二個 then
的值接收,並不會改變promsie的狀態,因此後面的 catch
便不會被觸發。
若是想觸發catch
也很簡單,只須要使用下面兩種方式的任何一種便可。
return new Promise().reject(1)
throw new Error('xxx')
Promise.resolve(1).then(function(v) { console.log(v); return new Error('error!!!'); }).then(function(v) { return Promise.reject(1); }).catch(function(e) { console.log(e); });
雖然觸發了最後的 catch
回調,可是否與 Promise
定義的標準相悖呢?畢竟 promsie對象的狀態一經發生,便沒法改變的...實際上並非如此,由於咱們以前已經說過, then、catch
等都會返回要給新的 promise
對象,並且這裏 return Promise.reject(1)
返回的自己就是一個新的對象。
Promise.resolve(1) .then(2) .then(Promise.resolve(3)) .then(console.log)
.then
或者 .catch
的參數指望是函數,傳入非函數則會發生值穿透。
參考
http://liubin.org/promises-book/ (開源Promise 迷你書)
https://zhuanlan.zhihu.com/p/30797777 Promise 必知必會(十道題)