JavaScript中的承诺——Promise

1、JavaScript单线程

  JavaScript运行在浏览器中,是以单线程的方式运行的;JavaScript为何不选择提升整个应用性能和吞吐量实现应用并行的多线程呢?javascript

  这在JavaScript的产生就决定了:当初JavaScript是用来处理用户和页面的交互,以及操做DOM树,CSS样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。若是是多线程的来操做DOM树,则会发生预想不到的冲突,一个线程A想要删除DOM节点,另一个线程B想要修改这个DOM节点,这样就会致使浏览器不知道听取哪个操做。因此JavaScript从诞生就决定了是单线程方式运行的。java

  由于单线程的执行,因此在同一时间内只能执行一个特定的任务,而且会阻塞其余任务。因此对于耗时很长的任务来讲,例如对于I/O设备的访问,其实并无必要等待它的完成,彻底能够在执行这项任务以前,JavaScript去执行另一个任务,直到I/O任务执行完成后再继续执行该任务的处理就好了。因此JavaScript对于这种耗时操做都会被处理为异步操做,以及回调注册机制,等到这些任务完成后就将后续的处理操做封装为JavaScript任务放入执行任务队列中,等待JavaScript线程空闲时去执行,所以这里就有了“浏览器事件循环”的机制promise

2、浏览器事件循环和回调机制

JavaScript不少任务都是异步的,包括键盘、鼠标I/O输入输出事件、Ajax请求网络I/O回调等。当这些异步的任务发生时,它们都会被放入浏览器事件任务队列中。在浏览器中有一个叫作消息循环池(Event Loop),JavaScript引擎在运行时候单线程的处理这些任务,它们会被放入在这个事件循环池中,须要等到JavaScript运行时执行线程空闲时候才会按照队列先进先出的原则被一一执行。可是因为此时JavaScript主线程也许并不空闲,因此它们不会被当即执行。浏览器

虽然JavaScript是单线程执行的,可是浏览器是多线程执行的,浏览器有JavaScript的执行线程、UI节点渲染线程,图片加载线程以及Ajax请求线程等等,在Chrome设计中存在不少的进程,并利用进程间通信来完成它们之间的同步,所以这也是Chrome快速的法宝之一。对于Ajax的请求也须要特殊线程来执行,当须要发送一个Ajax请求的时候,浏览器会开辟一个新的线程来执行HTTP的请求,它并不会阻塞JavaScript线程的执行,HTTP请求状态变动事件会被做为回调放入到浏览器的事件队列中等待被执行。服务器

 

3、异步出现的问题网络

由上咱们能够知道,JavaScript的不少任务都是异步处理的,以callback回调的方式处理事件任务,可是对于多个JavaScript异步任务的处理,将会碰到以下状况:多线程

 

 1 function1(data, function (data1){
 2 
 3     function2(data1, function (data2){
 4 
 5         function3(data2, function (data3){
 6                 // .... 一层套一层的回调
 7         });
 8 
 9     });
10 
11 });

 

咱们把这种每一层的回调函数都须要依赖上一层的回调执行完,因此造成了层层嵌套的关系,这样的现象叫作“回调地狱”,这样的代码很是不易于阅读与维护,为了解决这个问题,”Promise“出现了!异步

4、Promise的出现函数

1)Promise被翻译为”承诺“,它表示若是A调用了一个长时间的B任务的时候,B将会返回一个”承诺“给A,A就不用关心整个实施的过程,继续作本身的任务;当B实施完成的时候,会经过A,并将执行A之间的预先约定好的回调函数;Promise解决的问题是一种带有延迟的事件,这个事件会被延迟到将来某个合适的时间点在执行。oop

2)Promise有三种状态,分别是:Pending——初始状态,等到任务的完成或者拒绝;Fullfilled——任务执行完成而且成功状态;Rejected——任务执行完成而且失败的状态

3)Promise对象必须实现then方法,then是Promise规范的核心,并且then方法也必须返回一个Promise对象,同一个Promise对象能够注册多个then方法,而且回调的执行顺序跟他们注册的顺序一致

4)then方法接受两个回调函数:分别是成功时的回调和失败时候的回调;而且它们分别在:Promise由“Pending”状态转换到“Fulfilled”状态时被调用和在Promise由“Pending”状态转换到“Rejected”状态时被调用。

因此上述代码,使用Promise能够转换为:

function1(data)
    .then(function(data1){
        return function2(data1);
    })
    .then(function(data2){
       return function3(data2);
    })
    // 仍然能够继续then方法

Promise将原来回调地狱中的回调函数,从横向式增长变味了纵向增加。以链式的风格,使得代码更加可读和维护

5、Promise的使用

1)多个异步任务的串行处理

使用Angular中$http的实现:

$http.get('/demo1')
 .then(function(data){
     console.log('demo1', data);
     return $http.get('/demo2', {params: data.result});
  })
 .then(function(data){
     console.log('demo2', data);
     return $http.get('/demo3', {params: data.result});
  })
 .then(function(data){
     console.log('demo3', data.result);
  });

then方法能够一直延续下去,也能够在纵向扩展的途中改变为其余Promise的数据;

2)多个异步任务的并行处理

不少场景下,咱们须要处理的多个异步任务并无那么强的依赖关系,只须要在这一系列的异步任务所有完成的时候执行一些特定的逻辑。这个时候为了性能的考虑,咱们不须要将他们串行执行,选择并行是一个更好的选择。若是仍采用回调函数,则用Promise就能够解决

 

$q.all([$http.get('/demo1'),
        $http.get('/demo2'),
        $http.get('/demo3')
])
.then(function(results){
    console.log('result 1', results[0]);
    console.log('result 2', results[1]);
    console.log('result 3', results[2]);
});

 

这样就能够等到一堆异步任务完成后,在执行特定的业务回调了。

  PS:在Angular中的路由机制ngRouteuiRoute的resolve机制也是采用一样的原理:在路由执行的时候,会将获取模板的Promise、获取全部resolve数据的Promise都拼接在一块儿,同时并行的获取它们,而后等待它们都结束的时候,才开始初始化ng-viewui-view指令的scope对象,以及compile模板节点,并插入页面DOM中,完成一次路由的跳转而且切换了View,将静态的HTML模板变为动态的网页展现出来。

 

3)对于同步数据的Promise处理,统一调用接口

4)对于延迟任务的Promise DSL语义化封装

5)利用Promise来实现管道式AOP拦截

6、参考资料

1.http://greengerong.com/blog/2015/10/27/javascript-single-thread-and-browser-event-loop/

2.http://greengerong.com/blog/2015/10/22/promisede-miao-yong/

相关文章
相关标签/搜索