为了更像正常顺序那样处理异步模式,许多语言都包含一个有趣的方案库,称为承诺、延期或未来。JavaScript的承诺可以促进关注点的分离,而不是紧密耦合的接口。本文是关于基于承诺/A标准的JavaScript承诺。[http://wiki.commonjs.org/wiki/Promises/A]
承诺的用例:
执行规则、多次远程验证、超时处理、远程数据请求动画、事件逻辑与应用逻辑解耦、消除回调函数恐怖、三角控制、并行异步操作,JavaScript promise是一个承诺未来返回值的对象。是具有明确定义的行为的数据对象。有三种可能的承诺状态:
(待定)已拒绝(已拒绝)已解决(已完成)
被拒绝或履行的承诺属于已经解决的承诺。承诺只能从待定状态更改为已解决状态。此后,承诺状态保持不变。承诺可以在其相应的处理完成后很久才存在。也就是说,我们可以多次得到处理结果。我们通过调用promise.then()得到结果,在对应于promise的处理完成之前,这个函数不会返回。我们可以灵活地连接一堆承诺。这些串联的“然后”函数应该返回一个新的承诺或最早的承诺。有了这种风格,我们可以像写同步代码一样写异步代码。主要通过组合承诺来实现:
堆叠任务:分散在代码中的多个任务对应于同一个承诺。并行任务:多个承诺返回到同一个承诺。一个承诺,然后是另一个承诺。以上的组合。何必呢?就不能用基本的回调函数吗?
回调函数的问题
回调函数适用于简单的重复事件,例如根据点击验证表单,或者保存REST调用的结果。回调函数也使代码形成一个链,一个回调函数调用一个REST函数,为REST函数设置一个新的回调函数,然后REST函数调用另一个REST函数,以此类推。代码的水平增长大于垂直增长。回调函数看起来很简单,直到我们需要一个结果,这个结果是立即需要的,并在下一行计算中使用。
使用“严格”;var I=0;函数日志(数据){console.log('%d %s ',I,data);};函数validate() { log('等待它.');//四个长期运行的异步活动的序列setTimeout(函数(){ log('结果优先');setTimeout(函数(){ log(' result second '));setTimeout(函数(){ log(' result third '));setTimeout(function(){ log(' result third ')},1000);}, 1000);}, 1000);}, 1000);};validate();
我用超时来模拟异步操作。管理异常的方法是痛苦的,很容易玩弄下游的行为。当我们编写回调时,代码组织变得混乱。图2显示了一个模拟的验证流程可以在NodeJS REPL中运行。在下一节中,我们将从末日金字塔模式迁移到连续承诺模式。
我猜的
使用“严格”;var I=0;函数日志(数据){console.log('%d %s ',I,data);};//异步fn执行回调结果fnfunction async(arg,callBack){ setTimeout(function(){ log(' result ' arg);回调();}, 1000);};函数validate() { log('等待它.');//四个长时间运行的异步活动的序列async('第一'、function () { async('第二'、function () { async('第三'、function () { async('第四'、function(){ });});});});};validate();在NodeJS REPL执行的结果
$ node scripts/exampp2b . js1等待它.2结果第一3结果第二4结果第三5结果第四$
我曾经遇到过AngularJS动态验证的情况,根据对应表的值动态限制表单项的值。rest服务中定义了约束项的有效值范围。
我编写了一个调度程序来根据请求的值操作函数堆栈,以避免回调嵌套。调度程序从堆栈中弹出函数并执行它。函数的回调将在最后再次调用调度程序,直到堆栈被清空。每个回调记录从远程验证调用返回的所有验证错误。
我觉得我写的是反模式。如果我用Angular $http调用提供的promise,我的思维在整个验证过程中会更加线性,就像同步编程一样。平面承诺链是可读的。发生.使用承诺
采用kewpolice库。q库也适用。要使用这个库,首先使用npm将kew库导入NodeJS,然后将代码加载到NodeJS REPL。
我猜的
使用“严格”;var Q=require(' kew ');var I=0;函数日志(数据){console.log('%d %s ',I,data);};//异步fn返回一个promise function async(arg){ var delivered=q . deliver();setTimeout(function(){ delivered . resolve(' result ' arg); }, 1000);延期归还。};//扁平化承诺链函数validate() { log('等待它.');异步(“第一个”)。然后(函数(resp){ log(resp);返回异步(“秒”);}) .然后(函数(resp){ log(resp);返回async('第三')})。然后(函数(resp){ log(resp);返回async('第四');}) .然后(函数(resp){ log(resp);}).失败(日志);};validate();输出与使用嵌套回调时相同:
$ nodescripts/examp2-pflat.js1等待它.2个结果第一个3个结果第二个4个结果第三个5个结果第四个$这个代码有点“高”,但我认为更容易理解和修改。更容易添加正确的错误处理。在链的末端调用fail用于捕获链中的错误,但是我也可以在任何时候提供一个拒绝处理程序进行相应的处理。
服务器或浏览器
承诺在浏览器中和在NodeJS服务器中一样有效。在下面的地址中,http://JSFiddle.net/mauget/DnQDx/,指向了js小提琴的一个网页,该网页展示了如何使用承诺。js提琴中的所有代码都是可修改的。我是故意随意操作的。你可以尝试几次,得到相反的结果。它可以直接扩展到多个承诺链,就像前面的NodeJS例子一样。
平行承诺
考虑一个异步操作馈送另一个异步操作。让后者包括三个并行的异步行为,并依次馈送最后一个动作。只有当所有并行子请求都通过时,它们才能通过。它受到了十几个MongoDB操作的启发。有些是合格的并行操作。我实现了承诺的流程图。
我们如何模拟图形中心线上的平行承诺?关键是最大的承诺库有完整的功能,它生成一个包含一组子承诺的父承诺。当所有孩子的承诺都过去了,父母的承诺也就过去了。如果孩子承诺拒绝,父母承诺拒绝。
让十个平行的承诺各包含一个承诺字。只有当十个子类通过或者任何子类拒绝时,最后一个方法才能完成。
我猜的
var promiseVals=['To ',' be ',' or ',' not ',' To ',' be ',' that ',' is ',' the ','问题];var startparallel actions=function(){ var promises=[];//从每个字面量promisevals . foreach(function(value)){ promiss . push(makeAPromise(value))}中进行异步操作;});//将所有承诺合并成承诺的承诺返回Q.all(承诺);};StartParallelActions()。然后(.下面这个地址,http://jsfiddle.net/mauget/XKCy2/,在浏览器中为jshutch运行了十个并行的承诺,并随机拒绝或传递它们。有完整的代码用于检查和更改if条件。再跑一次,直到你到达相反的终点。
生下诺言
许多API用一个——函数返回promise。他们是天生的。通常我只处理函数的结果。然而,$q、mpromise和kew库有相同的API来创建、拒绝或传递承诺。有API文档链接到每个库的参考部分。我通常不需要构造承诺,除了本文中对包承诺和超时函数的未知描述。请参考我创造了哪些承诺。
承诺库互操作性
大多数JavaScript承诺库在当时的水平上进行互操作。你可以从一个外部承诺创造一个承诺,因为承诺可以包装任何类型的价值。然后可以支持跨库工作。除此之外,其他承诺功能可能有所不同。如果您需要一个您的库不包含的函数,您可以将基于您的库的承诺包装到基于包含您需要的函数的库创建的新承诺中。例如,JQuery的承诺有时会受到批评。然后可以在kew库中打包成q、$q、mpromise或promise进行操作。标签
现在我写了这篇文章,但一年前我是那个犹豫要不要拥抱承诺的人。我只想完成一项工作。我不想学习一个新的API或者破坏我原来的代码(因为我误解了诺言)。我以前也是这么想的!当我打了一点小赌,我轻松赢得了可喜的结果。
在本文中,我简单地给出了一个单个承诺、承诺链和并行承诺的例子。承诺很容易使用。如果我能使用它们,任何人都可以。要看完整的概念,我支持你点击专家写的参考指南。从Promises/A的引用开始,从事实标准JavaScript的Promise开始。