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中的Document文档对象
Jan 16 Javascript
jquery异步调用页面后台方法‏(asp.net)
Mar 01 Javascript
JS模拟面向对象全解(一、类型及传递)
Jul 13 Javascript
关于在IE下的一个安全BUG --可用于跟踪用户的系统鼠标位置
Apr 17 Javascript
jQuery 获取兄弟元素的几种不错方法
May 23 Javascript
javascript简单实现图片预加载
Dec 03 Javascript
ANGULARJS中用NG-BIND指令实现单向绑定的例子
Dec 08 Javascript
Highcharts入门之简介
Aug 02 Javascript
Javascript读取上传文件内容/类型/字节数
Apr 30 Javascript
vue-router 中 meta的用法详解
Nov 01 Javascript
Node如何后台数据库使用增删改查功能
Nov 21 Javascript
小程序使用分包的示例代码
Mar 23 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新手上路(十一)
2006/10/09 PHP
批量修改RAR文件注释的php代码
2010/11/20 PHP
PHP Cookie的使用教程详解
2013/06/03 PHP
php隐藏IP地址后两位显示为星号的方法
2014/11/21 PHP
PHP实现限制IP访问及提交次数的方法详解
2017/07/17 PHP
PHP mongodb操作类定义与用法示例【适合mongodb2.x和mongodb3.x】
2018/06/16 PHP
laravel-admin解决表单select联动时,编辑默认没选上的问题
2019/09/30 PHP
论坛里点击别人帖子下面的回复,回复标题变成“回复 24# 的帖子”
2009/06/14 Javascript
javascript中"/"运算符常见错误
2010/10/13 Javascript
js判断是否为ie的方法小结
2014/01/13 Javascript
JS遍历Json字符串中键值对先转成JSON对象再遍历
2014/08/15 Javascript
深入理解JavaScript系列(25):设计模式之单例模式详解
2015/03/03 Javascript
原生JS实现美图瀑布流布局赏析
2015/09/07 Javascript
jQuery+ajax实现滚动到页面底部自动加载图文列表效果(类似图片懒加载)
2016/06/07 Javascript
javascript中Date对象的使用总结
2016/11/21 Javascript
jQuery插件HighCharts绘制简单2D柱状图效果示例【附demo源码】
2017/03/21 jQuery
nodejs模块nodemailer基本使用-邮件发送示例(支持附件)
2017/03/28 NodeJs
JS实现自动轮播图效果(自适应屏幕宽度+手机触屏滑动)
2017/06/19 Javascript
深入理解vue Render函数
2017/07/19 Javascript
VueJs 将接口用webpack代理到本地的方法
2017/11/27 Javascript
详解关于html,css,js三者的加载顺序问题
2019/04/10 Javascript
Nuxt项目支持eslint+pritter+typescript的实现
2019/05/20 Javascript
JS实现json数组排序操作实例分析
2019/10/28 Javascript
Javascript实现html转pdf高清版(提高分辨率)
2020/02/19 Javascript
JavaScript鼠标悬停事件用法解析
2020/05/15 Javascript
ES6新增的数组知识实例小结
2020/05/23 Javascript
Python通过future处理并发问题
2017/10/17 Python
python opencv3实现人脸识别(windows)
2018/05/25 Python
python 成功引入包但无法正常调用的解决
2020/03/09 Python
django Layui界面点击弹出对话框并请求逻辑生成分页的动态表格实例
2020/05/12 Python
New Era英国官网:美国棒球帽品牌
2018/03/21 全球购物
软件配置管理有什么好处
2015/04/15 面试题
国家助学金获奖感言
2014/01/31 职场文书
中秋晚会策划方案
2014/06/12 职场文书
使用 Apache Superset 可视化 ClickHouse 数据的两种方法
2021/07/07 Servers
Python list列表删除元素的4种方法
2021/11/01 Python