zoukankan      html  css  js  c++  java
  • 闲话Promise机制

      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.status
    10                 }));
    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机制便是上述问题的一种解决方案。与他相关的规范有PromiseAPromiseA+,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': 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机制我们的编程方式可以变成这样:

     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 });

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

     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 });
    View Code

      

    Promise优点

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

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

    参考文章:

    javascript 异步编程

    jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)

    JavaScript异步编程原理

  • 相关阅读:
    API Hook完全手册
    ASP.NET页面刷新的实现方法
    ASP.NET验证码
    ASP.NET优化性能的方法
    Asp.net中Server.Transfer,Server.Execute和Response.Redirect的区别
    FireFox新标签页打开搜索和书签
    win10里如何在中文输入法里添加美式键盘
    Sublime Text 3中文乱码问题解决
    Win2008 r2 IIS7.5出现“FastCGI进程最近常常失败。请过一会再尝试此请求”的解决方法
    Sublime Text 3中设置不记住上次打开的文件
  • 原文地址:https://www.cnblogs.com/dojo-lzz/p/4340897.html
Copyright © 2011-2022 走看看