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 相关文章推荐
基于jquery的固定表头和列头的代码
May 03 Javascript
JavaScript在for循环中绑定事件解决事件参数不同的情况
Jan 20 Javascript
js使用html()或text()方法获取设置p标签的显示的值
Aug 01 Javascript
BootStrap的Datepicker控件使用心得分享
May 25 Javascript
用jQuery的AJax实现异步访问、异步加载
Nov 02 Javascript
JavaScript该如何学习 怎样轻松学习JavaScript
Jun 12 Javascript
Vue学习笔记进阶篇之单元素过度
Jul 19 Javascript
Vue Socket.io源码解读
Feb 07 Javascript
vue中使用axios post上传头像/图片并实时显示到页面的方法
Sep 27 Javascript
Jquery属性的获取/设置及样式添加/删除操作技巧分析
Dec 23 jQuery
微信小程序点击view动态添加样式过程解析
Jan 21 Javascript
原生javascript如何实现共享onload事件
Jul 03 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 运行效率总结(提示程序速度)
2009/11/26 PHP
php使用number_format函数截取小数的方法分析
2016/05/27 PHP
php is_executable判断给定文件名是否可执行实例
2016/09/26 PHP
PHP输出多个元素的排列或组合的方法
2017/03/14 PHP
JScript中的"this"关键字使用方式补充材料
2007/03/08 Javascript
如何快速的呈现我们的网页的技巧整理
2007/07/01 Javascript
jQuery validate 中文API 附validate.js中文api手册
2010/07/31 Javascript
Javascript类定义语法,私有成员、受保护成员、静态成员等介绍
2011/12/08 Javascript
js动态添加删除,后台取数据(示例代码)
2013/11/25 Javascript
JavaScript关闭当前页面(窗口)不带任何提示
2014/03/26 Javascript
javascript中Date对象应用之简易日历实现
2016/07/12 Javascript
Vue.js实现一个自定义分页组件vue-paginaiton
2016/09/05 Javascript
BootStrap入门教程(一)之可视化布局
2016/09/19 Javascript
jQuery插件HighCharts绘制2D金字塔图效果示例【附demo源码下载】
2017/03/09 Javascript
微信小程序调用PHP后台接口 解析纯html文本
2017/06/13 Javascript
JS实现获取进今年第几天是周几的方法分析
2018/06/27 Javascript
vue-cli 默认路由再子路由选中下的选中状态问题及解决代码
2018/09/06 Javascript
对angularJs中controller控制器scope父子集作用域的实例讲解
2018/10/08 Javascript
jsonp实现百度下拉框功能的方法分析
2019/05/10 Javascript
微信小程序引入Vant组件库过程解析
2019/08/06 Javascript
node.js使用mongoose操作数据库实现购物车的增、删、改、查功能示例
2019/12/23 Javascript
JavaScript对象原型链原理解析
2020/01/22 Javascript
JavaScript中的Proxy对象
2020/11/27 Javascript
[01:06:07]2014 DOTA2国际邀请赛中国区预选赛5.21 DT VS CIS
2014/05/22 DOTA
python实现指定ip端口扫描方式
2019/12/17 Python
python实现ftp文件传输功能
2020/03/20 Python
详解css position 5种不同的值的用法
2019/07/30 HTML / CSS
长辈证婚人证婚词
2014/01/09 职场文书
学习雷锋精神心得体会范文
2014/03/12 职场文书
全国税务系统先进集体事迹材料
2014/05/19 职场文书
交通安全标语
2014/06/06 职场文书
钱塘江大潮导游词
2015/02/03 职场文书
2015年管理人员工作总结
2015/05/13 职场文书
2019年鼓励无偿献血倡议书
2019/09/17 职场文书
Golang解析JSON对象
2022/04/30 Golang
云服务器部署 Web 项目的实现步骤
2022/06/28 Servers