JavaScript异步编程的Promise模式(转)
异步模式在web编程中变得越来越重要,对于web主流语言Javascript来说,这种模式实现起来不是很利索,为此,许多Javascript库(比如 jQuery和Dojo)添加了一种称为promise的抽象(有时也称之为deferred)。通过这些库,开发人员能够在实际编程中使用 promise模式。IE官方博客最近发表了一篇文章,详细讲述了如何使用XMLHttpRequest2来实践promise模式。我们来了解一下相关的概念和应用。
考虑这样一个例子,某网页存在异步操作(通过XMLHttpRequest2或者Web Workers)。随着Web 2.0技术的深入,浏览器端承受了越来越多的计算压力,所以“并发”具有积极的意义。对于开发人员来说,既要保持页面与用户的交互不受影响,又要协调页面与异步任务的关系,这种非线性执行的编程要求存在适应的困难。先抛开页面交互不谈,我们能够想到对于异步调用需要处理两种结果——成功操作和失败处理。在成功的调用后,我们可能需要把返回的结果用在另一个Ajax请求中,这就会出现“函数连环套”的情况(在笔者的另一篇文章《NodeJS的异步编程风格》中有详细的解释)。这种情况会造成编程的复杂性。看看下面的代码示例(基于XMLHttpRequest2):
 function searchTwitter(term, onload, onerror) {
      var xhr, results, url;
      url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
      xhr = new XMLHttpRequest();
      xhr.open('GET', url, true);
      xhr.onload = function (e) {
          if (this.status === 200) {
              results = JSON.parse(this.responseText);
              onload(results);
          }
      };
      xhr.onerror = function (e) {
          onerror(e);
      };
      xhr.send();
  }
  function handleError(error) {
      /* handle the error */
  }
  function concatResults() {
      /* order tweets by date */
  }
  function loadTweets() {
      var container = document.getElementById('container');
      searchTwitter('#IE10', function (data1) {
          searchTwitter('#IE9', function (data2) {
              /* Reshuffle due to date */
              var totalResults = concatResults(data1.results, data2.results);
              totalResults.forEach(function (tweet) {
                  var el = document.createElement('li');
                  el.innerText = tweet.text;
                  container.appendChild(el);
              });
          }, handleError);
      }, handleError);
  }
上面的代码其功能是获取Twitter中hashtag为IE10和IE9的内容并在页面中显示出来。这种嵌套的回调函数难以理解,开发人员需要仔细分析哪些代码用于应用的业务逻辑,而哪些代码处理异步函数调用的,代码结构支离破碎。错误处理也分解了,我们需要在各个地方检测错误的发生并作出相应的处理。
为了降低异步编程的复杂性,开发人员一直寻找简便的方法来处理异步操作。其中一种处理模式称为promise,它代表了一种可能会长时间运行而且不一定必须完整的操作的结果。这种模式不会阻塞和等待长时间的操作完成,而是返回一个代表了承诺的(promised)结果的对象。
考虑这样一个例子,页面代码需要访问第三方的API,网络延迟可能会造成响应时间较长,在这种情况下,采用异步编程不会影响整个页面与用户的交互。promise模式通常会实现一种称为then的方法,用来注册状态变化时对应的回调函数。比如下面的代码示例:
searchTwitter(term).then(filterResults).then(displayResults);
promise模式在任何时刻都处于以下三种状态之一:未完成(unfulfilled)、已完成(resolved)和拒绝(rejected)。以CommonJS Promise/A 标准为例,promise对象上的then方法负责添加针对已完成和拒绝状态下的处理函数。then方法会返回另一个promise对象,以便于形成promise管道,这种返回promise对象的方式能够支持开发人员把异步操作串联起来,如then(resolvedHandler, rejectedHandler); 。resolvedHandler 回调函数在promise对象进入完成状态时会触发,并传递结果;rejectedHandler函数会在拒绝状态下调用。
有了promise模式,我们可以重新实现上面的Twitter示例。为了更好的理解实现方法,我们尝试着从零开始构建一个promise模式的框架。首先需要一些对象来存储promise。
var Promise = function () {
        /* initialize promise */
    };
接下来,定义then方法,接受两个参数用于处理完成和拒绝状态。
Promise.prototype.then = function (onResolved, onRejected) {
     /* invoke handlers based upon state transition */
 };
同时还需要两个方法来执行理从未完成到已完成和从未完成到拒绝的状态转变。
 Promise.prototype.resolve = function (value) {
      /* move from unfulfilled to resolved */
  };
  Promise.prototype.reject = function (error) {
      /* move from unfulfilled to rejected */
  };
现在搭建了一个promise的架子,我们可以继续上面的示例,假设只获取IE10的内容。创建一个方法来发送Ajax请求并将其封装在promise中。这个promise对象分别在xhr.onload和xhr.onerror中指定了完成和拒绝状态的转变过程,请注意searchTwitter函数返回的正是promise对象。然后,在loadTweets中,使用then方法设置完成和拒绝状态对应的回调函数。
 function searchTwitter(term) {
     var url, xhr, results, promise;
     url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
     promise = new Promise();
     xhr = new XMLHttpRequest();
     xhr.open('GET', url, true);
     xhr.onload = function (e) {
         if (this.status === 200) {
             results = JSON.parse(this.responseText);
             promise.resolve(results);
         }
     };
     xhr.onerror = function (e) {
         promise.reject(e);
     };
     xhr.send();
     return promise;
 }
 function loadTweets() {
     var container = document.getElementById('container');
     searchTwitter('#IE10').then(function (data) {
         data.results.forEach(function (tweet) {
             var el = document.createElement('li');
             el.innerText = tweet.text;
             container.appendChild(el);
         });
     }, handleError);
 }
到目前为止,我们可以把promise模式应用于单个Ajax请求,似乎还体现不出promise的优势来。下面来看看多个Ajax请求的并发协作。此时,我们需要另一个方法when来存储准备调用的promise对象。一旦某个promise从未完成状态转化为完成或者拒绝状态,then方法里对应的处理函数就会被调用。when方法在需要等待所有操作都完成的时候至关重要。
 Promise.when = function () {
     /* handle promises arguments and queue each */
 };
以刚才获取IE10和IE9两块内容的场景为例,我们可以这样来写代码:
var container, promise1, promise2;
container = document.getElementById('container');
promise1 = searchTwitter('#IE10');
promise2 = searchTwitter('#IE9');
Promise.when(promise1, promise2).then(function (data1, data2) { /* Reshuffle due to date */
var totalResults = concatResults(data1.results, data2.results);
totalResults.forEach(function (tweet) {
var el = document.createElement('li');
el.innerText = tweet.text;
container.appendChild(el);
});
}, handleError);
分析上面的代码可知,when函数会等待两个promise对象的状态发生变化再做具体的处理。在实际的Promise库中,when函数有很多变种,比如 when.some()、when.all()、when.any()等,读者从函数名字中大概能猜出几分意思来,详细的说明可以参考CommonJS的一个promise实现when.js。
除了CommonJS,其他主流的Javascript框架如jQuery、Dojo等都存在自己的promise实现。开发人员应该好好利用这种模式来降低异步编程的复杂性。我们选取Dojo为例,看一看它的实现有什么异同。
Dojo框架里实现promise模式的对象是Deferred,该对象也有then函数用于处理完成和拒绝状态并支持串联,同时还有resolve和reject,功能如之前所述。下面的代码完成了Twitter的场景:
 function searchTwitter(term) {
     var url, xhr, results, def;
     url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
     def = new dojo.Deferred();
     xhr = new XMLHttpRequest();
     xhr.open('GET', url, true);
     xhr.onload = function (e) {
         if (this.status === 200) {
             results = JSON.parse(this.responseText);
             def.resolve(results);
         }
     };
     xhr.onerror = function (e) {
         def.reject(e);
     };
     xhr.send();
     return def;
 }
 dojo.ready(function () {
     var container = dojo.byId('container');
     searchTwitter('#IE10').then(function (data) {
         data.results.forEach(function (tweet) {
             dojo.create('li', {
                 innerHTML: tweet.text
             }, container);
         });
     });
 });
不仅如此,类似dojo.xhrGet方法返回的即是dojo.Deferred对象,所以无须自己包装promise模式。
 var deferred = dojo.xhrGet({
     url: "search.json",
     handleAs: "json"
 });
 deferred.then(function (data) {
     /* handle results */
 }, function (error) {
     /* handle error */
 });
除此之外,Dojo还引入了dojo.DeferredList,支持开发人员同时处理多个dojo.Deferred对象,这其实就是上面所提到的when方法的另一种表现形式。
 dojo.require("dojo.DeferredList");
 dojo.ready(function () {
     var container, def1, def2, defs;
     container = dojo.byId('container');
     def1 = searchTwitter('#IE10');
     def2 = searchTwitter('#IE9');
     defs = new dojo.DeferredList([def1, def2]);
     defs.then(function (data) {
         // Handle exceptions
         if (!results[0][0] || !results[1][0]) {
             dojo.create("li", {
                 innerHTML: 'an error occurred'
             }, container);
             return;
         }
         var totalResults = concatResults(data[0][1].results, data[1][1].results);
         totalResults.forEach(function (tweet) {
             dojo.create("li", {
                 innerHTML: tweet.text
             }, container);
         });
     });
 });
上面的代码比较清楚,不再详述。
说到这里,读者可能已经对promise模式有了一个比较完整的了解,异步编程会变得越来越重要,在这种情况下,我们需要找到办法来降低复杂度,promise模式就是一个很好的例子,它的风格比较人性化,而且主流的JS框架提供了自己的实现。所以在编程实践中,开发人员应该尝试这种便捷的编程技巧。需要注意的是,promise模式的使用需要恰当地设置promise对象,在对应的事件中调用状态转换函数,并且在最后返回promise对象。
技术社区对异步编程的关注也在升温,国内社区也发出了自己的声音。资深技术专家老赵就发布了一套开源的异步开发辅助库Jscex,它的设计很巧妙,抛弃了回调函数的编程方式,采用一种“线性编码、异步执行”的思想,感兴趣的读者可以查看这里。
不仅仅是前端的JS库,如今火热的NodeJS平台也出现了许多第三方的promise模块,具体的清单可以访问这里。
原文地址:http://www.infoq.com/cn/news/2011/09/js-promise/
JavaScript异步编程的Promise模式(转)的更多相关文章
- JavaScript异步编程的Promise模式
		参考: http://www.infoq.com/cn/news/2011/09/js-promise http://www.cnblogs.com/rubylouvre/p/3495286.html ... 
- javascript异步编程,promise概念
		javascript 异步编程 概述 采用单线程模式工作的原因: 避免多线dom操作同步问题,javascript的执行环境中负责执行代码的线程只有一个 内容概要 同步模式和异步模式 事件循环和消息队 ... 
- Javascript异步编程之三Promise: 像堆积木一样组织你的异步流程
		这篇有点长,不过干货挺多,既分析promise的原理,也包含一些最佳实践,亮点在最后:) 还记得上一节讲回调函数的时候,第一件事就提到了异步函数不能用return返回值,其原因就是在return语句执 ... 
- JavaScript异步编程的主要解决方案—对不起,我和你不在同一个频率上
		众所周知(这也忒夸张了吧?),Javascript通过事件驱动机制,在单线程模型下,以异步的形式来实现非阻塞的IO操作.这种模式使得JavaScript在处理事务时非常高效,但这带来了很多问题,比如异 ... 
- javascript异步编程的前世今生,从onclick到await/async
		javascript与异步编程 为了避免资源管理等复杂性的问题, javascript被设计为单线程的语言,即使有了html5 worker,也不能直接访问dom. javascript 设计之初是为 ... 
- Promises与Javascript异步编程
		Promises与Javascript异步编程 转载:http://www.zawaliang.com/2013/08/399.html 在如今都追求用户体验的时代,Ajax应用真的是无所不在.加上这 ... 
- 5分种让你了解javascript异步编程的前世今生,从onclick到await/async
		javascript与异步编程 为了避免资源管理等复杂性的问题,javascript被设计为单线程的语言,即使有了html5 worker,也不能直接访问dom. javascript 设计之初是 ... 
- 转: Promises与Javascript异步编程
		在如今都追求用户体验的时代,Ajax应用真的是无所不在.加上这些年浏览器技术.HTML5以及CSS3等的发展,越来越多的富Web应用出现:在给与我们良好体验的同时,Web开发人员在背后需要处理越来越多 ... 
- JavaScript异步编程
		前言 如果你有志于成为一个优秀的前端工程师,或是想要深入学习JavaScript,异步编程是必不可少的一个知识点,这也是区分初级,中级或高级前端的依据之一.如果你对异步编程没有太清晰的概念,那么我建议 ... 
随机推荐
- eclipse web(Spring+SpringMVC+Hibernate)项目迁移至intellij idea
			1.导入Eclipseweb项目 跟着导航一直下一步 出现警告不要担心,先点击确认,到后面再进行设置jdk 成功导入项目后如下图 2.对导入的项目进行配置按Ctrl+shift+alt+s(或下图中的 ... 
- Spring学习--通过注解配置 Bean (三)
			组件装配: <context:component-sacan> 元素还会自动注册 AutowiredAnnotationBeanPostProcesser 实例 , 该实例可以自动装配具有 ... 
- windows mysql 安装及启动
			0.下载: 
- DotNet 学习笔记 应用状态管理
			Application State Options --------------------------------------------------------------------- *Htt ... 
- python3 多态,绑定方法与非绑定方法
			多态:同一种事物的不同形态(一个抽象类有多个子类,因而多态的概念依赖于继承) 1. 序列类型有多种形态:字符串,列表,元组. 2. 动物有多种形态:人,狗,猪 多态性:多态性是指具有不同功能的函数可以 ... 
- cygwin与vim配置
			参考 http://www.jeepshoe.org/810958442.htm cygwin安装包管理器 通过终端安装apt-cyg之前选要安装以下软件包wget tar gawk bzip2 ap ... 
- JSOI2016酱油记
			高一,第一次参加NOIP. 后悔初中没有报过名...唉,后悔也来不及了. 不知道自己一个暑假干了什么...算法没学多少,脑子倒是越来越不好使了. 过了初赛,数周后一脸茫(meng)然(bi)地去考场. ... 
- python--requests_html
			这个模块从名字上也能看出来,是专门用来解析html的,这个和requests的作者是同一人,非常牛逼的一位大佬. 大致读了一下源码,总共一个py文件(但是引用了其他的模块),加上注释总共才700多行, ... 
- python Nosql-redis 连接、管道
			非关系型数据库和关系型数据库的差别: 非关系型数据库的优势: 性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高. 可扩展性同样也是因为基于 ... 
- JSP 基础之 JSTL <c:if>用法
			<c:if>还有另外两个属性var和scope.当我们执行<c:if>的时候,可以将这次判断后的结果存放到属性var里:scope则是设定var的属性范围.哪些情况才会用到va ... 
