系列文章 -- ES6筆記系列html
好久好久之前,在作Node.js聊天室,使用MongoDB數據服務的時候就遇到了多重回調嵌套致使代碼混亂的問題。jquery
JS異步編程有利有弊,Promise的出現,改善了這一格局,讓異步編程表現出相似「同步式代碼」的形式,更好地體現了它的價值。git
Promise是一種異步編程的解決方案,本質來講其實它是一種規範,Promises/A+規範es6
根據規範的定義,一個Promise對象應該至少有如下的基本特色github
三個狀態編程
Promise有三個狀態:Pending(進行中)、Resolved或Fulfilled(已完成)、Rejected(已失敗)api
其中:Pending爲Promise的初始狀態;當Resolved成功時,會調用onFulfilled方法;當Rejected失敗時,會調用onRejected方法數組
而且:狀態只能從Pending轉換爲Resolved狀態,或者從Pending轉換爲Rejected狀態,不存在其餘狀態間的轉換promise
Then方法瀏覽器
Promise必須提供一個then方法,用以訪問當前值、最終的返回值以及失敗的緣由
最基本的then方法接受兩個函數參數 promise.then(onFulfilled, onReject),對應於狀態變化到Resolved和Rejected時的函數調用
基於Promises/A+中規範的要求,能夠自行實現一個基本的promise對象
可參考 一步一步實現一個Promise
近年來,已經出現了不少Promise異步編程的插件,咱們可使用這些插件,常見的有:
例如使用jQuery新版Ajax模塊內置的Deferred使用到了Promise,咱們能夠直接這樣調用
// 回調函數的方式 $.get('url', function(rs) { rs = JSON.parse(rs); }); // Promise的形式 $.get('url').success(function(rs) { rs = JSON.parse(rs); })
不過jQuery中的Promise並非徹底按照Primises/A+規範來實現的,因此使用的時候可能會有問題,詳見
ES6原生引入了Promise,它在不少現代瀏覽器上已經獲得支持
在不支持原生Promise的環境下,除了能夠直接使用一些第三方Promise庫以外,還可使用這個插件來兼容低版本瀏覽器
其實,ES6中的原生Promise實現與RSVP.js有很大的關係,因此相關語法也和它相似
好比在爬蟲開發時,先獲取用戶資料,再獲取該用戶的文章,則能夠用Promise,用相似如下的結構實現
function getUser(id) { return new Promise(function(resolve, reject) { $.get('/user?id=' + id, function(rs) { rs = JSON.parse(rs); if (rs.status !== 200) { reject(rs); } else { resolve(rs); } }); }); } function getContent(user) { return new Promise(function(resolve, reject) { $.get('/content', { user: user }, function(rs) { rs = JSON.parse(rs); if (rs.status !== 200) { reject(rs); } else { resolve(rs); } }); }); } getUser(11).then(function(rs) { return getContent(rs.user); }).catch(function(rs) { throw Error(rs.msg); }).then(function(rs) { console.log(rs.content); }).catch(function(rs) { throw Error(rs.msg); });
成功調用getUser以後,能夠經過return 返回getContent(rs.user)這個promise對象,繼續接下去的執行任務
除了直接返回這個新的promise對象,咱們也能夠直接返回一個數據,這個數據將會做爲下一函數調用時的參數,且看例子:
function step(num) { return new Promise(function(resolve, reject) { setTimeout(function() { if (num > 0) { resolve(num); } else { reject(0); } }) }); } step(-1).then(function(num) { console.log('resolve ' + num); return -5; }, function(num) { console.log('reject ' + num); // reject 0 return 5; }).then(step) // 下一個要執行的任務操做 .then(function(num) { console.log('resolve ' + num); // resolve 5 }, function(num) { console.log('reject ' + num); });
當參數的數值爲正數時,則直接resolve返回該數值,若是爲負數則reject返回0,初始數值爲-1,因此調用了reject
再看另外一個例子:
function log(n) { return new Promise(function(resolve, reject) { setTimeout(function() { if (n % 2) { resolve('奇數:' + n); } else { reject('偶數:' + n); } }, 1000); }); } log(1).then(function(data) { console.log(data); return log(2); }, function(err) { console.log(err); return log(3); }).then(function(data) { console.log(data); }, function(err) { console.log(err); });
以上代碼執行以後
下面來詳細介紹原生Promise的使用方法
new Promise(func)
經過實例化構造函數成一個promise對象,構造函數中有個函數參數,函數參數爲(resolve, reject)的形式,供以函數內resolve成功以及reject失敗的調用
.then(onFulfilled, onRejected)
then方法,方法帶兩個參數,可選,分別爲成功時的回調以及失敗時的回調
如上代碼,log(1)時執行了resolve,log(2)時執行了reject
.catch(onRejected)
catch方法,方法帶一個參數,爲失敗時的回調。其實.catch方法就是 .then(undefined, onRejected)的簡化版,經過例子看看它的特色
function log(n) { return new Promise(function(resolve, reject) { setTimeout(function() { if (n % 2) { resolve('奇數:' + n); } else { reject('偶數:' + n); } }, 1000); }); } log(2).then(function(data) { console.log(data); return log(3); }).catch(function(err) { console.log(err); });
看這個例子,then中只有一個參數,調用log(2)以後reject執行,到達catch中輸出
再看一個栗子,代碼換成如下兩種,輸出都同樣
log(1).then(function(data) { console.log(data); return log(2); }).catch(function(err) { console.log(err); });
log(1).then(function(data) { console.log(data); return log(2); }).then(undefined, function(err) { console.log(err); });
如此一來,第一輪log(1)的resolve後,自行調用log(2),從而執行reject,經過catch執行相應的輸出
Promise.all()方法
Promise.all()方法接受一個promise的數組對象,只有數組中全部的promise都執行成功,整個promise纔算成功,若是數組對象中有個promise執行失敗,則整個promise就失敗
看這個簡單的例子,意圖是調用log(1,2,3,4,5)這個promise完成以後再調用log(6),其中相應值小於3就resolve,反之就reject
1 function log() { 2 var promises = []; 3 4 [...arguments].forEach(function(n) { 5 promises.push(new Promise(function(resolve, reject) { 6 7 var info = ''; 8 setTimeout(function() { 9 if (n < 3) { 10 info = '小於3 resolve:' + n; 11 console.log(info); 12 resolve(info); 13 } else { 14 info = 'reject:' + n; 15 console.log(info); 16 reject(info); 17 } 18 }, 1000); 19 })); 20 }); 21 22 return Promise.all(promises); 23 } 24 25 log(1, 2, 3, 4, 5).then(function(data) { 26 console.log(data); 27 return log(6); 28 }).catch(function(err) { 29 console.log(err); 30 });
首先,依次將相應實例化的promise對象存入promises數組,經過Promise.all()調用返回,執行結果爲
由輸出結果知,1和2被resolve,三、四、5被reject,整個數組裏已經有多於一個的promise對象被reject,僅僅觸發了catch中的回調,因此log(6)得不到執行
Promise.race()方法
與Promise.all()相似,它也接受一個數組對象做爲參數,但其意義不同
只有數組中全部的promise都執行失敗,整個promise纔算失敗,若是數組對象中有個promise執行成功,則整個promise就成功
把上述代碼的all換成race,執行結果爲:
由輸出結果知,1和2被resolve,三、四、5被reject,整個數組裏已經有多於一個的promise對象被resolve,觸發了then中成功的回調,log(6)獲得調用執行
由於這時尚未額外的then或catch方法來監視log(6)的狀態,因此僅僅輸出的在log函數中執行的結果
Promise.resolve()方法
除了在實例化Promise構造函數內部使用resolve以外,咱們還能夠直接調用resolve方法
var promise = Promise.resolve('resolve one'); // var promise = Promise.reject('reject one'); promise.then(function(data) { console.log(data); // resolve one }).catch(function(err) { console.log(err); });
參數除了能夠直接指定值以外,還能夠是一個Promise實例,具備then方法的對象,或者爲空
參數爲Promise實例,則將包裝後返回該Promise實例
var promise = Promise.resolve($.get('url'));
前文說到jQuery的Promise實現方式並非徹底按照規範來着,經過Promise.resolve的包裝,能夠返回一個規範化的Promise實例
參數爲空,則直接返回一個狀態爲resolved|fulfilled的Promise對象
setTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one');
直接resolve的Promise對象是在本輪事件循環結束時執行,setTimeout是在下一輪事件循環結束時執行,因此輸出爲:
參數爲一個具備then方法的對象,則自動將這個對象轉換爲Promise對象並調用該then方法,如
Promise.resolve({ then: function(resolve, reject) { resolve('resolved'); } }).then(function(data) { console.log(data); // resolved }).catch(function(err) { console.log(err); });
Promise.reject()方法
除了在實例化Promise構造函數內部使用reject以外,咱們還能夠直接調用reject方法
相似於Promise.resolve()中參數的多樣化,且看如下幾個栗子:
Promise.resolve({ then: function(resolve, reject) { reject('rejected'); } }).then(function(data) { console.log(data); }).catch(function(err) { console.log(err); // rejected });
setTimeout(function () { console.log('three'); }, 0); Promise.reject().catch(function () { console.log('two'); }); console.log('one'); // one // two // three
var promise = Promise.reject($.get('url'));
// var promise = Promise.resolve('resolve one'); var promise = Promise.reject('reject one'); promise.then(function(data) { console.log(data); }).catch(function(err) { console.log(err); // reject one });
關於Promise有不少難點技巧點,好比如下四中調用方式的區別
doSomething().then(function () { return doSomethingElse(); }); doSomethin().then(functiuoin () { doSomethingElse(); }); doSomething().then(doSomethingElse()); doSomething().then(doSomethingElse);
相關解釋見:談談使用 promise 時候的一些反模式