JavaScript中的Promise使用详解


Posted in Javascript onJune 24, 2015

许多的语言,为了将异步模式处理得更像平常的顺序,都包含一种有趣的方案库,它们被称之为promises,deferreds,或者futures。JavaScript的promises ,可以促进关注点分离,以代替紧密耦合的接口。 本文讲的是基于Promises/A 标准的JavaScript promises。[http://wiki.commonjs.org/wiki/Promises/A]

Promise的用例:

  •     执行规则
  •     多个远程验证
  •     超时处理
  •     远程数据请求
  •     动画
  •     将事件逻辑从应用逻辑中解耦
  •     消除回调函数的恐怖三角
  •     控制并行的异步操作

JavaScript promise是一个承诺将在未来返回值的对象。是具有良好定义的行为的数据对象。promise有三种可能的状态:

  1.     Pending(待定)
  2.     Rejected(拒绝)
  3.     Resolved(已完成)

一个已经拒绝或者完成的承诺属于已经解决的。一个承诺只能从待定状态变成已经解决的状态。之后,承诺的状态就不变了。承诺可以在它对应的处理完成之后很久还存在。也就是说,我们可以多次取得处理结果。我们通过调用promise.then()来取得结果,这个函数一直到承诺对应的处理结束才会返回。我们可以灵活的串联起一堆承诺。这些串联起来的“then”函数应该返回一个新的承诺或者最早的那个承诺。
通过这个样式,我们可以像写同步代码一样来写非同步代码。主要是通过组合承诺来实现:

  •     堆栈式任务:多处散落在代码中的,对应同一个承诺。
  •     并行任务:多个承诺返回同一个承诺。
  •     串行任务:一个承诺,然后接着执行另一个承诺。
  •     上面几种的组合。

为什么要这么麻烦?只用基本的回调函数不行吗?

回调函数的问题

回调函数适合简单的重复性事件,例如根据点击来让一个表单有效,或者保存一个REST调用的结果。回调函数还会使代码形成一个链,一个回调函数调用一个REST函数,并为REST函数设置一个新的回调函数,这个新的回调函数再调用另一个REST函数,依此类推。代码的横向增长大于纵向的增长。回调函数看起来很简单,直到我们需要一个结果,而且是立刻就要,马上就用在下一行的计算中。

'use strict';
var i = 0;
function log(data) {console.log('%d %s', ++i, data); };
 
function validate() {
  log("Wait for it ...");
  // Sequence of four Long-running async activities
  setTimeout(function () {
   log('result first');
   setTimeout(function () {
     log('result second');
     setTimeout(function () {
      log('result third');
      setTimeout(function () {
        log('result fourth')
      }, 1000);
     }, 1000);
   }, 1000);
  }, 1000);
 
};
validate();

我使用timeout来模拟异步操作。管理异常的方法是痛苦的,很容易玩漏下游行为。当我们编写回调,那么代码组织变得混乱。图2显示了一个模拟验证流可以运行在NodeJS REPL。在下一节,我们将从pyramid-of-doom模式迁移到一个连续的promise。

Figure
 

'use strict';
var i = 0;
function log(data) {console.log('%d %s', ++i, data); };
 
// Asynchronous fn executes a callback result fn
function async(arg, callBack) {
  setTimeout(function(){
   log('result ' + arg);
   callBack();
  }, 1000);
};
 
function validate() {
  log("Wait for it ...");
  // Sequence of four Long-running async activities
  async('first', function () {
   async('second',function () {
     async('third', function () {
      async('fourth', function () {});
     });
   });
  });
};
validate();

在NodeJS REPL执行的结果
 

$ node scripts/examp2b.js
1 Wait for it ...
2 result first
3 result second
4 result third
5 result fourth
$

 

我曾经遇到一个AngularJS动态验证的情况,根据对应表的值,动态的限制表单项的值。限制项的有效值范围被定义在REST服务上。

我写了一个调度器,根据请求的值,去操作函数栈,以避免回调嵌套。调度器从栈中弹出函数并执行。函数的回调会在结束时重新调用调度器,直到栈被清空。每次回调都记录所有从远程验证调用返回的验证错误。

我认为我写的玩意儿是一种反模式。如果我用Angular的$http调用提供的promise,在整个验证过程中我的思维会更近似线性形式,就像同步编程。平展的promise链是可读的。继续...
 
使用Promises

其中采用了kew promise库。Q库同样适用。要使用该库,首先使用npm将kew库导入到NodeJS,然后加载代码到NodeJS REPL。

Figure
 

'use strict';
var Q = require('kew');
var i = 0;
 
function log(data) {console.log('%d %s', ++i, data); };
 
// Asynchronous fn returns a promise
function async(arg) {
  var deferred = Q.defer();
  setTimeout(function () {
    deferred.resolve('result ' + arg);\
  }, 1000);
  return deferred.promise;
};
 
// Flattened promise chain
function validate() {
  log("Wait for it ...");
  async('first').then(function(resp){
    log(resp);
    return async('second');
  })
  .then(function(resp){
    log(resp);
    return async('third')
  })
  .then(function(resp){
    log(resp);
    return async('fourth');
  })
  .then(function(resp){
    log(resp);
  }).fail(log);
};
validate();

输出和使用嵌套回调时相同:
 

$ node scripts/examp2-pflat.js
1 Wait for it ...
2 result first
3 result second
4 result third
5 result fourth
$

该代码稍微“长高”了,但我认为更易于理解和修改。更易于加上适当的错误处理。在链的末尾调用fail用于捕获链中错误,但我也可以在任何一个then里面提供一个reject的处理函数做相应的处理。

服务器 或 浏览器

Promises在浏览器中就像在NodeJS服务器中一样有效。下面的地址, http://jsfiddle.net/mauget/DnQDx/,指向JSFiddle的一个展示如何使用一个promise的web页面。 JSFiddle所有的代码是可修改的。我故意操作随意动作。你可以试几次得到相反的结果。它是可以直接扩展到多个promise链, 就像前面NodeJS例子。

JavaScript中的Promise使用详解

并行 Promises

考虑一个异步操作喂养另一个异步操作。让后者包括三个并行异步行为,反过来,喂最后一个行动。只有当所有平行的子请求通过才能通过。这是灵感来自偶遇一打MongoDB操作。有些是合格的并行操作。我实现了promises的流流程图。

JavaScript中的Promise使用详解

我们怎么会模拟那些在该图中心行的并行promises?关键是,最大的promise库有一个全功能,它产生一个包含一组子promises的父promie。当所有的子promises通过,父promise通过。如果有一个子promise拒绝,父promise拒绝。
 

让十个并行的promises每个都包含一个文字promise。只有当十个子类通过或如果任何子类拒绝,最后的then方法才能完成。

Figure
 

var promiseVals = ['To ', 'be, ', 'or ',
  'not ', 'to ', 'be, ', 'that ',
  'is ', 'the ', 'question.'];
 
var startParallelActions = function (){
  var promises = [];
 
  // Make an asynchronous action from each literal
  promiseVals.forEach(function(value){
    promises.push(makeAPromise(value));
  });
 
  // Consolidate all promises into a promise of promises
  return Q.all(promises);
};
 
startParallelActions ().then( . . .

下面的地址, http://jsfiddle.net/mauget/XKCy2/,针对JSFiddle在浏览器中运行十个并行promises,随机的拒绝或通过。这里有完整的代码用于检查和变化if条件。重新运行,直到你得到一个相反的完成。JavaScript中的Promise使用详解

孕育 Promise

许多api返回的promise都有一个then函数——他们是thenable。通常我只是通过then处理thenable函数的结果。然而,$q,mpromise,和kew库拥有同样的API用于创建,拒绝,或者通过promise。这里有API文档链接到每个库的引用部分。我通常不需要构造一个promise,除了本文中的包装promise的未知描述和timeout函数。请参考哪些我创建的promises。

Promise库互操作

大多数JavaScript promise库在then级别进行互操作。你可以从一个外部的promise创建一个promise,因为promise可以包装任何类型的值。then可以支持跨库工作。除了then,其他的promise函数则可能不同。如果你需要一个你的库不包含的函数,你可以将一个基于你的库的promise包装到一个新的,基于含有你所需函数的库创建的promise里面。例如,JQuery的promise有时为人所诟病。那么你可以将其包装到Q,$q,mpromise,或者kew库的promise中进行操作。
 
结语

现在我写了这篇文章,而一年前我却是犹豫要不要拥抱promise的那个。我只是单纯地想完成一项工作。 我不想学习新的API,或是打破我原来的代码(因为误解了promise)。我曾经如此错误地认为!当我下了一点注时,就轻易就赢得了可喜的成果。

在这篇文章中,我已经简单给出了一个单一的promise,promise链,和一个并行的promise的promise的的例子。 Promises不难使用。如果我可以使用它们,任何人都可以。 要查看完整的概念,我支持你点击专家写的参考指南。从Promises/A 的参考开始,从事实上的标准JavaScript的Promise 开始。

Javascript 相关文章推荐
Javascript-Mozilla和IE中的一个函数直接量的问题分析
Aug 12 Javascript
一个JS小玩意 几个属性相加不能超过一个特定值.
Sep 29 Javascript
json格式的时间显示为正常年月日的方法
Sep 08 Javascript
jQuery+ajax实现无刷新级联菜单示例
May 21 Javascript
JavaScript中string对象
Jun 12 Javascript
逐一介绍Jquery data()、Jquery stop()、jquery delay()函数(详)
Nov 04 Javascript
JS简单实现浮动窗口效果示例
Sep 07 Javascript
js querySelector() 使用方法
Dec 21 Javascript
jquery实现左右轮播切换效果
Jan 01 jQuery
JS设计模式之访问者模式定义与用法分析
Feb 05 Javascript
详解ES6中的 Set Map 数据结构学习总结
Nov 06 Javascript
JS removeAttribute()方法实现删除元素的某个属性
Jan 11 Javascript
JavaScript面对国际化编程时的一些建议
Jun 24 #Javascript
对JavaScript的全文搜索实现相关度评分的功能的方法
Jun 24 #Javascript
在Mac OS下使用Node.js的简单教程
Jun 24 #Javascript
在Node.js应用中使用Redis的方法简介
Jun 24 #Javascript
浅析Node.js中使用依赖注入的相关问题及解决方法
Jun 24 #Javascript
浅析Node.js中的内存泄漏问题
Jun 23 #Javascript
充分发挥Node.js程序性能的一些方法介绍
Jun 23 #Javascript
You might like
Php连接及读取和写入mysql数据库的常用代码
2014/08/11 PHP
PHP+redis实现的悲观锁机制示例
2018/06/12 PHP
jQuery 常见开发使用技巧总结
2009/12/26 Javascript
jquery 可拖拽的窗体控件实现代码
2010/03/21 Javascript
jQuery循环滚动新闻列表示例代码
2014/06/17 Javascript
JQ技术实现注册页面带有校验密码强度
2015/07/27 Javascript
javascript加载xml 并解析各节点的值(实现方法)
2016/10/12 Javascript
js实现tab切换效果
2017/02/16 Javascript
@ResponseBody 和 @RequestBody 注解的区别
2017/03/08 Javascript
Angular.js中angular-ui-router的简单实践
2017/07/18 Javascript
VueJS 集成 Medium Editor的示例代码 (自定义编辑器按钮)
2017/08/24 Javascript
基于 Vue.js 2.0 酷炫自适应背景视频登录页面实现方式
2018/01/17 Javascript
vue select选择框数据变化监听方法
2018/08/24 Javascript
node微信开发之获取access_token+自定义菜单
2019/03/17 Javascript
页面内锚点定位及跳转方法总结(推荐)
2019/04/24 Javascript
js实现继承的方法及优缺点总结
2019/05/08 Javascript
vue input输入框关键字筛选检索列表数据展示
2020/10/26 Javascript
vue实现修改图片后实时更新
2019/11/14 Javascript
基于JavaScript实现表格隔行换色
2020/05/08 Javascript
Python如何生成树形图案
2018/01/03 Python
python 从csv读数据到mysql的实例
2018/06/21 Python
Python使用QQ邮箱发送邮件实例与QQ邮箱设置详解
2020/02/18 Python
python matplotlib:plt.scatter() 大小和颜色参数详解
2020/04/14 Python
Selenium python时间控件输入问题解决方案
2020/07/22 Python
css3编写浏览器背景渐变背景色的方法
2018/03/05 HTML / CSS
整理HTML5的一些新特性与Canvas的常用属性
2016/01/29 HTML / CSS
Html5让容器充满屏幕高度或自适应剩余高度的布局实现
2020/05/14 HTML / CSS
销售主管岗位职责
2014/02/08 职场文书
党员先锋岗事迹材料
2014/05/08 职场文书
体现团队精神的口号
2014/06/06 职场文书
标准单位租车协议书
2014/09/23 职场文书
医药公司采购员岗位职责
2015/04/03 职场文书
2015年七一建党节活动方案
2015/05/05 职场文书
python基础入门之字典和集合
2021/06/13 Python
python随机打印成绩排名表
2021/06/23 Python
《模拟人生4》推出新补丁 “婚礼奇缘”DLC终于得到修复
2022/04/03 其他游戏