―Rapunzel许多语言,为了更像正常序列一样处理异步模式,包含一个有趣的提议库,称为promises、deferreds或futures。JavaScriptpromises,它可以促进关注点分离,而不是紧密耦合的接口。本文是关于基于Promises/A标准的JavaScriptpromises。[http://wiki.commonjs.org/wiki/Promises/A]Promise用例:执行规则多个远程验证超时处理远程数据请求动画事件逻辑与应用逻辑解耦消除回调函数的恐怖三角控制并行异步操作JavaScriptpromise是一个承诺在未来返回值的对象。是具有明确定义的行为的数据对象。一个承诺有三种可能的状态:PendingRejectedResolved一个被拒绝或完成的承诺被解决。promise只能从pending状态变为resolved状态。之后,promise的状态不变。承诺可以在其相应处理完成后长期存在。也就是说,我们可以多次得到处理结果。我们通过调用promise.then()获得结果,直到promise的处理完成后它才会返回。我们可以灵活地将一堆promise串起来。这些串联的“then”函数应该返回一个新的promise或原始的promise。使用这种风格,我们可以像编写同步代码一样编写异步代码。主要通过组合promise来实现:Stackedtasks:多个分散在代码中,对应同一个promise。并行任务:多个承诺返回相同的承诺。串行任务:一个承诺,然后是另一个承诺。以上的组合。何必呢?不能只使用基本的回调函数吗?回调函数的问题回调函数适用于简单的重复事件,例如根据单击验证表单或保存REST调用的结果。回调函数也是让代码形成一条链,一个回调函数调用一个REST函数,为REST函数设置一个新的回调函数,这个新的回调函数又调用另一个REST函数,以此类推。这形成了如图1所示的破坏金字塔。代码的水平增长大于垂直增长。回调函数看起来很简单,直到我们需要一个结果,我们马上就需要它,它用于下一行的计算。图1:销毁Pyramid'usestrict';变量=0;函数日志(数据){console.log('%d%s',++i,数据);};functionvalidate(){log("Waitforit...");//四个长时间运行的异步活动的顺序setTimeout(function(){log('resultfirst');setTimeout(function(){log('resultsecond');setTimeout(function(){log('resultthird');setTimeout(function(){log('resultfourth')},1000);},1000);},1000);},1000);};validate();在图1中,我使用超时来模拟异步操作。管理异常的方式很痛苦,很容易遗漏下游行为。当我们编写回调时,代码组织变得混乱。图2显示了可以在NodeJSREPL中运行的模拟验证流程。在下一节中,我们将从厄运金字塔模式迁移到持续承诺。图'usestrict';vari=0;functionlog(data){console.log('%d%s',++i,data);};//异步fnexecutesacallbackresultfnfunctionasync(arg,callBack){setTimeout(function(){log('result'+arg);callBack();},1000);};functionvalidate(){log("Waitforit...");//SequenceofourLong-runningasyncactivitiesasync('first',function(){async('second',function(){async('third',function(){async('fourth',function(){});});});});};validate();在NodeJS中执行REPLResult$nodescripts/examp2b.js1Waitforit...2resultfirst3resultsecond4resultthird5resultfourth$曾经遇到过AngularJS动态校验的情况,根据对应表的值动态限制表单项的值。约束的有效值范围在REST服务上定义。我写了一个调度器,根据请求的值来操作函数栈,避免回调嵌套。调度程序从堆栈中弹出函数并执行它。该函数的回调将在最后重新调用调度程序,直到堆栈被清除。每个回调记录从远程验证调用返回的所有验证错误。我认为我写的是反模式。如果我使用Angular的$http调用提供的promise,我的思维在整个验证过程中会更加线性,就像同步编程一样。扁平化的承诺链是可读的。继续...使用承诺图3显示了我如何将验证重写为承诺链。它使用kewpromise库。这同样适用于Q库。要使用该库,首先使用npm将kew库导入NodeJS,然后将代码加载到NodeJSREPL。图'usestrict';varQ=require('kew');vari=0;functionlog(data){console.log('%d%s',++i,data);};//Asynchronousfnreturnspromisefunctionasync(arg){vardeferred=Q.defer();setTimeout(function(){deferred.resolve('result'+arg);\},1000);returndeferred.promise;};//Flattenedpromisechainfunctionvalidate(){log("等待...");async('first').then(函数(resp){log(resp);returnasync('second');}).then(function(resp){log(resp);returnasync('third')}).then(函数(resp){log(resp);returnasync('第四');}).then(函数(resp){log(resp);}).fail(log);};validate();输出与使用嵌套回调时相同:$nodescripts/examp2-pflat.js1Waitforit...2resultfirst3resultsecond4resultthird5resultfourth$这段代码略显“高大上”,但我认为更容易理解和修改。更容易添加正确的错误处理。在链尾调用fail是用来捕获链中的错误,不过我也可以在anythen中提供一个rejecthandler来做相应的处理。#p#服务器或浏览器Promises在浏览器中的工作与在NodeJS服务器中一样好。以下URLhttp://jsfiddle.net/mauget/DnQDx/指向一个展示如何使用承诺的JSFiddle网页。JSFiddle中的所有代码都是可修改的。浏览器输出的变化如图4所示。我故意执行随机操作。您可以尝试几次以获得相反的结果。扩展到多个承诺链很简单,就像前面的NodeJS示例一样。图4.单一承诺并行性Promises考虑一个异步操作提供另一个异步操作。让后者由三个并行的异步操作组成,这三个操作反过来又提供一个操作。仅当所有并行子请求都通过时。如图5所示。这是偶然遇到的十几个MongoDB操作的启发。有些有资格进行并行操作。我实现了承诺流程图。图5:异步操作的结构我们如何模拟图中中间一行的并行承诺?关键是,最新的promise库有一个完整的函数,可以生成一个包含一组子promise的父promise。当所有子承诺都实现时,父承诺就会实现。如果存在拒绝的子承诺,则父承诺会拒绝。图6显示了一个代码片段,它做出十个并行承诺,每个承诺都包含一个字面承诺。最后一个then方法仅在十个子类通过或任何子类拒绝时才完成。varpromiseVals=['To','be,','or','not','to','be,','that','is','the','question.'];varstartParallelActions=function(){varpromises=[];//MakeanasynchronousactionfromeachliteralpromiseVals.forEach(function(value){promises.push(makeAPromise(value));});//ConsolidateallpromisesintoapromiseofpromisesreturnQ.all(promises);};startParallelActions().then(...下面的地址,http://jsfiddle.net/mauget/XKCy2/,在浏览器中针对JSFiddle运行十个并行承诺,随机拒绝或通过。这是检查和更改if条件的完整代码。重新运行直到你得到相反的实现。图7显示了正结果。图7:JSFiddle并行承诺示例生成Promise许多API返回具有then函数的承诺-它们是thenable。通常我只是通过then处理thenable函数的结果。但是,$q、mpromise和kew库具有用于创建、拒绝或传递承诺的相同API。以下是链接到每个库的参考部分的API文档。除了本文中包装承诺的未知描述和超时函数外,我通常不需要构造承诺。请参考我创建的承诺。Promise库互操作性大多数JavaScriptPromise库在then级别进行互操作。您可以从外部承诺创建承诺,因为承诺可以包装任何类型的值。然后可以支持跨库工作。除了then,其他的promise函数可能会有所不同。如果您需要您的库中不包含的函数,您可以将基于您的库的承诺包装到基于包含您需要的函数的库的新承诺中。例如,JQuery的promises有时会受到批评。然后就可以包装成Q,$q,mpromise,或者kew库的promise进行操作。结论既然我正在写这篇文章,一年前我还是那个对接受承诺犹豫不决的人。我只是想完成一份工作。我不想学习新的API,也不想破坏我的旧代码(因为误解了承诺)。我以前就这么想错了!当我下一点赌注时,这既轻松又令人欣慰。在本文中,我简要给出了单个承诺、承诺链和并行承诺的示例。Promise并不难使用。如果我可以使用它们,任何人都可以。要查看完整的概念,我鼓励您点击阅读专家撰写的参考指南。从对Promises/A的引用开始,这是事实上的标准JavaScriptPromise。如果您还没有直接使用过promises,请尝试一下。下定决心:您将获得良好的体验。我保证!–LouMauget,asktheteam@keyholesoftware.com参考链接http://wiki.commonjs.org/wiki/Promises/Ahttps://github.com/bellbind/using-promise-q/https://github.com/中/kewhttps://docs.angularjs.org/api/ng/service/$qhttps://github.com/aheckmann/mpromisehttp://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt2-practical-usehttps://gist.github.com/domenic/3889970http://sitr.us/2012/07/31/promise-pipelines-in-javascript.htmlhttp://dailyjs.com/2014/02/20/promises-in-detail/https://www.promisejs.org/http://solutionoptimist.com/2013/12/27/javascript-promise-chains-2/http://www.erights.org/elib/distrib/pipeline.htmlhttp://zeroturnaround.com/rebellabs/monadic-futures-in-java8/
