JavaScript中的Promise使⽤详解
Promise的⽤例:
执⾏规则
多个远程验证
超时处理
javascript 函数远程数据请求
动画
将事件逻辑从应⽤逻辑中解耦
消除回调函数的恐怖三⾓
控制并⾏的异步操作
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 () {
}, 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
考虑⼀个异步操作喂养另⼀个异步操作。让后者包括三个并⾏异步⾏为,反过来,喂最后⼀个⾏动。只有当所有平⾏的⼦请求通过才能通过。这是灵感来⾃偶遇⼀打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( . . .
孕育 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 开始。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论