Promise的誕生與Javascript中異步編程息息相關,js中異步編程主要指的是setTimout/setInterval、DOM事件機制、ajax,經過傳入回調函數實現控制反轉。異步編程爲js帶來強大靈活性的同時,也帶來了嵌套回調的問題。詳細來講主要有兩點,第一嵌套太深代碼可讀性太差,第二並行邏輯必須串行執行。javascript
1 request = function(url, cb, eb) { 2 var xhr = new XMLHttpRequest(); 3 xhr.onreadystatechange = function() { 4 if (xhr.readyState === 4) { 5 if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) { 6 cb(xhr.responseText); 7 } else { 8 eb(new Error({ 9 message: xhr.status 10 })); 11 } 12 } 13 }; 14 xhr.open('get', url, true); 15 xhr.send(null); 16 }
這個例子中程序要依次處理data一、data二、data3,嵌套太多可讀性太差html
1 //回調函數嵌套過深 2 request('data1.json', function(data1){ 3 console.log(data1);//處理data1 4 request('data2.json', function(data2) { 5 console.log(data2);//處理data2 6 request('data3.json', function(data3) { 7 console.log(data3);//處理data3 8 9 alert('success'); 10 }, function(err) { 11 console.error(err); 12 }); 13 }, function(err) { 14 console.error(err); 15 }); 16 }, function(err) { 17 console.error(err); 18 });
這個例子中程序須要請求data一、data二、data3數據,獲得三個數據後才進行下一步處理。數據並不須要串行請求,但咱們的代碼卻須要串行執行,增長了等待時間。前端
1 //並行邏輯串行執行 2 request('data1', function(data1) { 3 request('data2', function(data2) { 4 request('data3', function(data3) { 5 console.log(data1, data2, data3);//處理所有數據 6 7 alert('success'); 8 }, function(err) { 9 console.error(err); 10 }); 11 }, function(err) { 12 console.error(err); 13 }); 14 }, function(err) { 15 console.error(err); 16 });
Promise機制java
Promise機制即是上述問題的一種解決方案。與他相關的規範有PromiseA和PromiseA+,PromiseA中對Promise進行了總體描述,PromiseA+對A進行了補充,在then函數的行爲方面進行了更加詳盡的闡述。ajax
then
method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.跟promise交互的主要方式是經過他的then方法來註冊回調函數去接收promise的最終結果值或者是promise不能完成的緣由。
咱們能夠簡單總結一下規範。每一個promise都有三個狀態:pending(默認)、fulfilled(完成)、rejected(失敗);默認狀態能夠轉變爲完成態或失敗態,完成態與失敗態之間沒法相互轉換,轉變的過程是不可逆的,轉變一旦完成promise對象就不能被修改。經過promise提供的then函數註冊onFulfill(成功回調)、onReject(失敗回調)、onProgres(進度回調)來與promise交互。Then函數返回一個promise對象(稱爲promise2,前者成爲promise1),promise2受promise1狀態的影響,具體請查看A+規範。編程
上兩個規範中並無說明promise的狀態如何改變,大部分前端框架中使用Deferred來改變promise的狀態(resolve()、reject())。兩者關係請看下圖。json
這裏根據規範,咱們實現一下promise數組
1 Promise = function() { 2 this.queue = []; 3 this.value = null; 4 this.status = 'pending';// pending fulfilled rejected 5 }; 6 7 Promise.prototype.getQueue = function() { 8 return this.queue; 9 }; 10 Promise.prototype.getStatus = function() { 11 return this.status; 12 }; 13 Promise.prototype.setStatus = function(s, value) { 14 if (s === 'fulfilled' || s === 'rejected') { 15 this.status = s; 16 this.value = value || null; 17 this.queue = []; 18 var freezeObject = Object.freeze || function(){}; 19 freezeObject(this);// promise的狀態是不可逆的 20 } else { 21 throw new Error({ 22 message: "doesn't support status: " + s 23 }); 24 } 25 }; 26 Promise.prototype.isFulfilled = function() { 27 return this.status === 'fulfilled'; 28 }; 29 Promise.prototype.isRejected = function() { 30 return this.status === 'rejected'; 31 } 32 Promise.prototype.isPending = function() { 33 return this.status === 'pending'; 34 } 35 Promise.prototype.then = function(onFulfilled, onRejected) { 36 var handler = { 37 'fulfilled': onFulfilled, 38 'rejected': onRejected 39 }; 40 handler.deferred = new Deferred(); 41 42 if (!this.isPending()) {//這裏容許先改變promise狀態後添加回調 43 utils.procedure(this.status, handler, this.value); 44 } else { 45 this.queue.push(handler);//then may be called multiple times on the same promise;規範2.2.6 46 } 47 return handler.deferred.promise;//then must return a promise;規範2.2.7 48 }; 49 50 var utils = (function(){ 51 var makeSignaler = function(deferred, type) { 52 return function(result) { 53 transition(deferred, type, result); 54 } 55 }; 56 57 var procedure = function(type, handler, result) { 58 var func = handler[type]; 59 var def = handler.deferred; 60 61 if (func) { 62 try { 63 var newResult = func(result); 64 if (newResult && typeof newResult.then === 'function') {//thenable 65 // 此種寫法存在閉包容易形成內存泄露,咱們經過高階函數解決 66 // newResult.then(function(data) { 67 // def.resolve(data); 68 // }, function(err) { 69 // def.reject(err); 70 // }); 71 //PromiseA+規範,x表明newResult,promise表明def.promise 72 //If x is a promise, adopt its state [3.4]: 73 //If x is pending, promise must remain pending until x is fulfilled or rejected. 74 //If/when x is fulfilled, fulfill promise with the same value. 75 //If/when x is rejected, reject promise with the same reason. 76 newResult.then(makeSignaler(def, 'fulfilled'), makeSignaler(def, 'rejected'));//此處的本質是利用了異步閉包 77 } else { 78 transition(def, type, newResult); 79 } 80 } catch(err) { 81 transition(def, 'rejected', err); 82 } 83 } else { 84 transition(def, type, result); 85 } 86 }; 87 88 var transition = function(deferred, type, result) { 89 if (type === 'fulfilled') { 90 deferred.resolve(result); 91 } else if (type === 'rejected') { 92 deferred.reject(result); 93 } else if (type !== 'pending') { 94 throw new Error({ 95 'message': "doesn't support type: " + type 96 }); 97 } 98 }; 99 100 return { 101 'procedure': procedure 102 } 103 })(); 104 105 Deferred = function() { 106 this.promise = new Promise(); 107 }; 108 109 Deferred.prototype.resolve = function(result) { 110 if (!this.promise.isPending()) { 111 return; 112 } 113 114 var queue = this.promise.getQueue(); 115 for (var i = 0, len = queue.length; i < len; i++) { 116 utils.procedure('fulfilled', queue[i], result); 117 } 118 this.promise.setStatus('fulfilled', result); 119 }; 120 121 Deferred.prototype.reject = function(err) { 122 if (!this.promise.isPending()) { 123 return; 124 } 125 126 var queue = this.promise.getQueue(); 127 for (var i = 0, len = queue.length; i < len; i++) { 128 utils.procedure('rejected', queue[i], err); 129 } 130 this.promise.setStatus('rejected', err); 131 }
經過Promise機制咱們的編程方式能夠變成這樣:promise
1 request = function(url) { 2 var def = new Deferred(); 3 4 var xhr = new XMLHttpRequest(); 5 xhr.onreadystatechange = function() { 6 if (xhr.readyState === 4) { 7 if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) { 8 def.resolve(xhr.responseText) 9 } else {//簡化ajax,沒有提供錯誤回調 10 def.reject(new Error({ 11 message: xhr.status 12 })); 13 } 14 } 15 }; 16 xhr.open('get', url, true); 17 xhr.send(null); 18 19 return def.promise; 20 } 21 22 request('data1.json').then(function(data1) { 23 console.log(data1);//處理data1 24 return request('data2.json'); 25 }).then(function(data2) { 26 console.log(data2);//處理data2 27 return request('data3.json'); 28 }, function(err) { 29 console.error(err); 30 }).then(function(data3) { 31 console.log(data3); 32 alert('success'); 33 }, function(err) { 34 console.error(err); 35 });
對於並行邏輯串行執行問題咱們能夠這樣解決ruby
1 //全部異步操做都完成時,進入完成態, 2 //其中一項異步操做失敗則進入失敗態 3 all = function(requestArray) { 4 // var some = Array.prototype.some; 5 var def = new Deferred(); 6 var results = []; 7 var total = 0; 8 requestArray.some(function(r, idx) { 9 //爲數組中每一項註冊回調函數 10 r.then(function(data) { 11 if (def.promise.isPending()) { 12 total++; 13 results[idx] = data; 14 15 if (total === requestArray.length) { 16 def.resolve(results); 17 } 18 } 19 }, function(err) { 20 def.reject(err); 21 }); 22 //若是不是等待狀態則中止,好比requestArray[0]失敗的話,剩下數組則不用繼續註冊 23 return !def.promise.isPending(); 24 }); 25 26 return def.promise; 27 } 28 29 all( 30 [request('data1.json'), 31 request('data2.json'), 32 request('data3.json')] 33 ).then( 34 function(results){ 35 console.log(results);// 處理data1,data2,data3 36 alert('success'); 37 }, function(err) { 38 console.error(err); 39 });
如下是幾個測試案例
1 //鏈式調用 2 var p1 = new Deferred(); 3 p1.promise.then(function(result) { 4 console.log('resolve: ', result); 5 return result; 6 }, function(err) { 7 console.log('reject: ', err); 8 return err; 9 }).then(function(result) { 10 console.log('resolve2: ', result); 11 return result; 12 }, function(err) { 13 console.log('reject2: ', err); 14 return err; 15 }).then(function(result) { 16 console.log('resolve3: ', result); 17 return result; 18 }, function(err) { 19 console.log('reject3: ', err); 20 return err; 21 }); 22 p1.resolve('success'); 23 //p1.reject('failed'); 24 p1.promise.then(function(result) { 25 console.log('after resolve: ', result); 26 return result; 27 }, function(err) { 28 console.log('after reject: ', err); 29 return err; 30 }).then(function(result) { 31 console.log('after resolve2: ', result); 32 return result; 33 }, function(err) { 34 console.log('after reject2: ', err); 35 return err; 36 }).then(function(result) { 37 console.log('after resolve2: ', result); 38 return result; 39 }, function(err) { 40 console.log('after reject2: ', err); 41 return err; 42 }); 43 44 //串行異步 45 var p2 = new Deferred(); 46 p2.promise.then(function(result) { 47 var def = new Deferred(); 48 setTimeout(function(){ 49 console.log('resolve: ', result); 50 def.resolve(result); 51 }) 52 return def.promise; 53 }, function(err) { 54 console.log('reject: ', err); 55 return err; 56 }).then(function(result) { 57 var def = new Deferred(); 58 setTimeout(function(){ 59 console.log('resolve2: ', result); 60 def.reject(result); 61 }) 62 return def.promise; 63 }, function(err) { 64 console.log('reject2: ', err); 65 return err; 66 }).then(function(result) { 67 console.log('resolve3: ', result); 68 return result; 69 }, function(err) { 70 console.log('reject3: ', err); 71 return err; 72 }); 73 p2.resolve('success'); 74 75 //並行異步 76 var p1 = function(){ 77 var def = new Deferred(); 78 setTimeout(function() { 79 console.log('p1 success'); 80 def.resolve('p1 success'); 81 }, 20); 82 83 return def.promise; 84 } 85 var p2 = function(){ 86 var def = new Deferred(); 87 setTimeout(function() { 88 console.log('p2 failed'); 89 def.reject('p2 failed'); 90 }, 10); 91 92 return def.promise; 93 } 94 95 var p3 = function(){ 96 var def = new Deferred(); 97 setTimeout(function() { 98 console.log('p3 success'); 99 def.resolve('p3 success'); 100 }, 15); 101 102 return def.promise; 103 } 104 105 all([p1(), p2(), p3()]).then(function(results) { 106 console.log(results); 107 }, function(err) { 108 console.error(err); 109 });
Promise優勢
對比使用Promise先後咱們能夠發現,傳統異步編程經過嵌套回調函數的方式,等待異步操做結束後再執行下一步操做。過多的嵌套致使意大利麪條式的代碼,可讀性差、耦合度高、擴展性低。經過Promise機制,扁平化的代碼機構,大大提升了代碼可讀性;用同步編程的方式來編寫異步代碼,保存線性的代碼邏輯,極大的下降了代碼耦合性而提升了程序的可擴展性。
Note:下圖是我整理的dojo/Deferred模塊的脈絡圖,使用dojo的道友能夠看一下
參考文章: