闲话Promise机制

来源:互联网 发布:淘宝销售小票打印模板 编辑:程序博客网 时间:2024/06/04 19:40

Promise的诞生与Javascript中异步编程息息相关,js中异步编程主要指的是setTimout/setInterval、DOM事件机制、ajax,通过传入回调函数实现控制反转。异步编程为js带来强大灵活性的同时,也带来了嵌套回调的问题。详细来说主要有两点,第一嵌套太深代码可读性太差,第二并行逻辑必须串行执行。

复制代码
 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.status10                 }));11             }12         }13     };14     xhr.open('get', url, true);15     xhr.send(null);16 }
复制代码

  这个例子中程序要依次处理data1、data2、data3,嵌套太多可读性太差

复制代码
 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 });
复制代码

  这个例子中程序需要请求data1、data2、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机制

  Promise机制便是上述问题的一种解决方案。与他相关的规范有PromiseA和PromiseA+,PromiseA中对Promise进行了整体描述,PromiseA+对A进行了补充,在then函数的行为方面进行了更加详尽的阐述。

PromiseA+规范
promise represents the eventual result of an asynchronous operation. 
一个promise代表了一个异步操作的最终结果
The primary way of interacting with a promise is through its 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())。二者关系请看下图。

  这里根据规范,我们实现一下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': procedure102     }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机制我们的编程方式可以变成这样:

复制代码
 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.status12                 }));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);//处理data124     return request('data2.json');25 }).then(function(data2) {26     console.log(data2);//处理data227     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 });
复制代码

  对于并行逻辑串行执行问题我们可以这样解决

复制代码
 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,data336             alert('success');37     }, function(err) {38         console.error(err);39     });
复制代码

  以下是几个测试案例

 View Code

  

Promise优点

对比使用Promise前后我们可以发现,传统异步编程通过嵌套回调函数的方式,等待异步操作结束后再执行下一步操作。过多的嵌套导致意大利面条式的代码,可读性差、耦合度高、扩展性低。通过Promise机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。

 

Note:下图是我整理的dojo/Deferred模块的脉络图,使用dojo的道友可以看一下

 

0 0
原创粉丝点击