JavaScript中的Promise使用详解
作者:goldensun 发布时间:2024-04-18 10:53:52
许多的语言,为了将异步模式处理得更像平常的顺序,都包含一种有趣的方案库,它们被称之为promises,deferreds,或者futures。JavaScript的promises ,可以促进关注点分离,以代替紧密耦合的接口。 本文讲的是基于Promises/A 标准的JavaScript promises。[http://wiki.commonjs.org/wiki/Promises/A]
Promise的用例:
执行规则
多个远程验证
超时处理
远程数据请求
动画
将事件逻辑从应用逻辑中解耦
消除回调函数的恐怖三角
控制并行的异步操作
JavaScript promise是一个承诺将在未来返回值的对象。是具有良好定义的行为的数据对象。promise有三种可能的状态:
Pending(待定)
Rejected(拒绝)
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例子。
并行 Promises
考虑一个异步操作喂养另一个异步操作。让后者包括三个并行异步行为,反过来,喂最后一个行动。只有当所有平行的子请求通过才能通过。这是灵感来自偶遇一打MongoDB操作。有些是合格的并行操作。我实现了promises的流流程图。
我们怎么会模拟那些在该图中心行的并行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条件。重新运行,直到你得到一个相反的完成。
孕育 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 开始。
猜你喜欢
- 简介在某些场景下,我们需要初始化一些资源,例如单例对象、配置等。实现资源的初始化有多种方法,如定义 package 级别的变量、在 init
- 前言 FTP(File Transfer Protocol)是文件传输协议的简称。用于Internet上的控制文件的双向传输。同时,它也是一
- 本文实例讲述了php中数字、字符与对象判断函数用法。分享给大家供大家参考。具体分析如下:在php判断数字,字符,对象,数组等包括有参见 is
- 1. File->Setting 打开如下所示的对话框,可以在Editor->General->Font下设置
- 简介这里通过python的绘图工具Matplotlib包可视化实现机器学习中的KNN算法。 需要提前安装python的Numpy和Matpl
- 1.在模板中,我们经常要使用一些url,实现页面之间的跳转,比如某个a标签中需要定义href属性。当然如果通过硬编码的方式直接将这个url固
- 一:编译器 编译器是一种特殊的程序,它可以把以特定编程语言写成的程序变为机器可以运行的机器码。我们把一个程序写好,这时我们利用的环境是文本编
- 首先,单表的UPDATE语句:UPDATE [LOW_PRIORITY] [IGNORE] tbl_nameSET col_name1=ex
- 二次移动平均法逻辑二次移动平均法是一种重要的数学工具,用于处理时间序列数据,它的主要目的是通过平滑序列中的噪音数据来更好地捕捉趋势。具体实现
- 和数据库打交道要频繁地用到 SQL 语句,除非你是全部用控件绑定的方式,但采用控件绑定的方式存在着灵活性差、效率低、功能
- Function ChkInvaildWord(Words) Const InvaildWords=&quo
- 在asp中调用sql server的存储过程可以加快程序运行速度,本文介绍了asp使用存储过程的方法。1.调用存储过程的一般方法 先假设在s
- 如下所示:RuntimeError: stack expects each tensor to be equal size, but got
- pandas的qcut可以把一组数字按大小区间进行分区,比如data = pd.Series([0,8,1,5,3,7,2,6,10,4,9
- PHP使用缓存即时输出内容(output buffering)的方法。分享给大家供大家参考。具体如下:$buffer = ini_get(&
- 大家都知道索引对于数据访问的性能有非常关键的作用,都知道索引可以提高数据访问效率。为什么索引能提高数据访问性能?他会不会有“副作用”?是不是
- 在网上有很多相关主题的讨论,但是一般都是用Iframe和XMLHTTP来实现。Iframe的实现可能是最常看到的。很多论坛和聊天室的无刷新效
- 当我们用javascript写ajax程序写得很“开心”的时候,突然有人告诉你有一种东西叫jquery,它会告诉你不直接和HttpReque
- 1.InnoDB的锁定机制InnoDB存储引擎支持行级锁,支持事务处理,事务是有一组SQL语句组成的逻辑处理单元,他的ACID特性如下:原子
- 一、引用返回引用返回用在当想用函数找到引用应该被绑定在哪一个变量上面时。不要用返回引用来增加性能,引擎足够聪明来自己进行优化。仅在有合理的技