“当我承诺某件事时,我永远都不会违反这一承诺。 决不。” ― 长发公主
许多语言都有有趣的方案库,称为promise,defers或future。 这些帮助将狂野的异步驯服为更平凡的顺序。 JavaScript许诺可以促进关注点的分离,而不是紧密耦合的接口。 本文是关于Promises / A变体JavaScript承诺的。 [ http://wiki.commonjs.org/wiki/Promises/A ]
承诺用例:
- 执行规则
- 多个远程验证
- 处理超时
- 远程数据请求
- 动画
- 将事件逻辑与应用逻辑分离
- 消除厄运的回调三角形
- 控制并行异步操作
JavaScript promise是将来要返回值的IOU。 它是具有明确定义的行为的数据对象。 一个承诺具有以下三种可能状态之一:
- 待定
- 被拒绝
- 解决
被拒绝或解决的诺言得以解决 。 一个承诺状态只能从未决状态变为已解决状态。 此后,其状态是不可变的。 承诺可以在其相关的行动解决之后很长时间才能实现。 有空时,我们可以多次提取结果。 我们通过调用promise.then()来实现这一点。 该呼叫将不会返回,除非或直到相关动作已解决为止。 我们可以流畅地兑现承诺。 链接的“ then”函数应各自返回一个promise,或让原始promise作为返回值。
通过这种范例,我们可以编写更多异步代码,就好像它是同步代码一样。 力量在于编写承诺任务:
- 堆叠的任务:多个然后分散在代码中,但承诺相同。
- 并行任务:多个promise返回一个promise。
- 顺序任务: 答应……然后……答应
- 这些的组合 。
为什么要增加这一层? 为什么我们不能只使用原始回调?
回调问题
回调适用于对简单的重复事件做出反应,例如基于单击启用表单值,或者用于存储REST调用的结果。 回调还通过使一个回调进行下一个REST调用,从而提供一个回调来进行下一个REST调用,从而使人们能够在链中进行编码,依此类推。 这趋向于如图1所示的厄运金字塔。在那里,代码水平增长比垂直增长快。 回调看似简单......直到我们需要的结果, 现在 ,在我们的代码下一步使用。
'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();
在图1中,我使用超时来模拟异步操作。 在那儿管理可能与下游动作可控地起作用的异常的想法是痛苦的。 当我们必须编写回调时,代码组织就会变得混乱。 图2显示了粘贴到NodeJS REPL中时将运行的模拟验证流程。 在下一节中,我们将其从“金字塔金字塔”模式迁移到顺序的Promise再现。
'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选项,那么我对整个验证的想法将类似于类似于同步编程的线性形式。 扁平的承诺链是可读的。 阅读...
使用承诺
图3显示了我将人为的验证重铸到了Promise链中。 它使用kew promise库。 Q库同样工作良好。 要尝试,请首先使用npm将kew库导入NodeJS,然后将代码加载到NodeJS REPL中。
'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
$
该代码有点“讲价”,但我认为它更易于理解和修改。 添加合理的错误处理更加容易。 在链内的链捕获错误的年底失效的电话,但我可以提供同时在任何一个拒绝处理,然后以应对其行动拒绝。
服务器或浏览器
承诺在浏览器以及NodeJS服务器中都是有用的。 以下URL http://jsfiddle.net/mauget/DnQDx/指向一个JSFiddle,该JSFiddle显示了如何在网页中使用单个Promise。 所有代码都可以在JSFiddle中进行修改。 浏览器输出的一种变化形式如图4所示。我操纵该动作以随机拒绝。 您可以尝试几次以得到相反的结果。 就像前面的NodeJS示例一样,将其扩展到多个Promise链将很简单。
并行承诺
考虑异步操作提供另一个异步操作。 让后者由三个并行的异步动作组成,这些动作又提供最终动作。 仅当所有并行子请求都解决时,它才会解决。 参见图5。这是从十二个MongoDB操作链的良好相遇中得到的启发。 有些人有资格并行运行。 我实现了承诺。
我们将如何在该图的中心行建模那些并行的承诺? 关键是大多数promise库具有一个all函数,该函数会生成数组中保存的子promise的parentpromise。 当所有孩子的诺言都解决时,父母的诺言就解决了。 如果孩子的一个诺言被拒绝,父母的诺言被拒绝。
图6显示了一个代码片段,该片段将十个文字转换为十个并行的承诺。 然后 ,只有当所有十个孩子都解决或任何一个孩子拒绝时, then结束才能完成。
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( . . .
以下URL http://jsfiddle.net/mauget/XKCy2/面向JSFiddle,该JSFiddle在浏览器中运行10个并行的Promise,随机拒绝或解决。 完整的代码在那里进行检查和假设更改。 重新运行,直到完成相反的操作。 图7显示了积极的结果。
生一个诺言
许多API返回具有then函数的promise –它们是thenable 。 通常我可以将一个then链接到一个可行的函数的结果上。 否则,$ q,mpromise,Q和kew库具有用于创建,拒绝或解决承诺的简单API。 参考部分中提供了每个库的API文档链接。 我通常不需要构造一个Promise,除了包装本文中忽略Promise的字面量和超时函数外。 请参阅我创建诺言的示例。
无极图书馆互通
大多数JavaScript承诺库在那时都可以互操作。 您可以从外国承诺中创建承诺,因为承诺可以包装任何类型的价值。 该工程跨库,支持即可 。 从那时起,还有不同的承诺功能。 如果您需要一个库未包含的功能,则可以将库中的promise包装在具有所需功能的库中的新promise中。 例如,JQuery Promise在文献中有时会受到损害。 您可以立即将每个包装在Q,$ q,mpromise或kew promise中以在该库中进行操作。
最后
我写这篇文章的时候是一年前不愿接受承诺的人。 我只是想把工作做好。 由于误解了诺言,我不想学习新的API或不想破坏我的代码。 我曾经做错吗! 当我一毛钱下来时,我轻松地取得了可喜的成绩。
在本文中,我给出了单个诺言,诺言链和诺言的并行诺言的简单示例。 承诺不难使用。 如果我可以使用它们,任何人都可以。 为了充实概念,我鼓励您单击Promise专家提供的参考文献。 从Promises / A参考(JavaScript承诺的事实上的标准)开始。
如果您尚未直接使用承诺,请尝试一下。 解决:您将拥有良好的体验。 我承诺!
翻译自: https://www.javacodegeeks.com/2014/08/javascript-promises-are-cool.html