(本文章以as3代码为例)前端
在前端开发时,常常会使用到Ajax(Asynchronous Javascript And XML)请求向服务器查询信息(get)或交换数据(post),ajax请求都是异步响应的,每次请求都不能同步返回结果,并且屡次请求嵌套在一块儿时,逻辑很难处理,怎么办呢?git
在as3中,get请求的写法一般以下github
public static function httpGet(url:String):void { var httpService:HTTPService =new HTTPService(); httpService.url= url; httpService.resultFormat="e4x"; httpService.method = URLRequestMethod.GET; httpService.addEventListener(ResultEvent.RESULT, onSuccess); httpService.addEventListener(FaultEvent.FAULT, onFail); httpService.send(); function onSuccess(result:ResultEvent):void { // do something
} function onFail(fault:FaultEvent):void { // alert error
} }
在ajax请求中,查询成功的回调函数是异步返回的,没法在HttpGet方法中返回结果,下一步的逻辑处理只能写在onSuccess方法中,对于查询结果的逻辑处理都要写在查询成功的回调函数内,若是业务逻辑很复杂的话,这种写法就太麻烦了,并且不能复用。ajax
一种解决思路是经过消息来传递查询结果,当查询成功或失败时,发送相应的消息和结果给该次查询的监听者,代码以下(注意红色加粗部分)编程
var eventBus:EventDispatcher = new EventDispatcher; public static function httpGetWithMessage(url:String, successMessage:String, failMessage:String):void { var httpService:HTTPService =new HTTPService(); httpService.url= url; httpService.resultFormat="e4x"; httpService.method = URLRequestMethod.GET; httpService.addEventListener(ResultEvent.RESULT, onSuccess); httpService.addEventListener(FaultEvent.FAULT, onFail); httpService.send(); function onSuccess(result:ResultEvent):void { eventBus.dispatchEvent(successMessage, result); } function onFail(fault:FaultEvent):void { eventBus.dispatchEvent(failMessage, fault); } } private function action(url:String):void { var successMSG:String = "success"; var failMSG:String = "fail"; eventBus.addEventListener(successMSG, onSuccess); eventBus.addEventListener(failMSG, onFail); httpGetWithMessage(url, successMSG, failMSG); } private function onSuccess(result:ResultEvent):void { // do something
} private function onFail(fault:FaultEvent):void { // alert error
}
经过消息机制的办法,能够把查询成功和失败的回调函数从action方法中提取出来,从而能够复用这部分代码,可是,使用消息机制仍然存在4个缺点:promise
1、必须有一个全局消息总线来控制全部的消息。当查询次数多时,须要为每次查询定义不一样的消息,还要考虑并发时同一个业务请求的消息不能相同,这种全局消息总线对于消息的管理代价太大;服务器
2、action方法仍然不能复用,每次不一样的查询都须要从新写一个新的方法;并发
3、action方法仍然是异部处理,方法自己没法返回查询结果,以至程序的先后语意不连贯。当请求次数多时,对于一个业务逻辑的处理,必需要分开写在不少个回调函数内,这些回调函数彼此之间也没法沟通。框架
4、最重要的一点是,当一个业务逻辑处理须要屡次查询时,每次查询只能嵌套在上一次查询的成功回调函数内,如此,除了最内层的查询能够复用,外内全部的查询方法都不能复用,代码极难维护,这时若是须要修改两个查询的前后顺序,你就惨了。异步
Promise/Deferred模式 (协议/延时模式)
Promise/Deferred模式最先出如今Javascript的Dojo框架中,它是对异步编程的一种抽象。
promise的核心思想能够归纳为:把异部处理看做一个协议,不管异部处理的结果是成功仍是失败,都把协议提早返回,等异部处理结束后,协议的接收者天然就知道结果是成功仍是失败了。从形式上说,Promise/Deferred模式能够把异部处理用同步的形式表达出来,极大方便了代码维护,语意更加清晰,也方便了代码复用。
这里是两个开源地址:
将文章最初的get请求方法用Promise/Deferred模式改写一下,首先new一个延时,在发出请求后当即返回,此时这个延时的状态是“未完成”,当异部请求成功后,回调函数会改变它的状态为“完成”或“失败”并传递参数,这样一来,异部逻辑就巧妙的变成了同步逻辑,代码以下
public static function httpGet(url:String):Promise { var deferred:Deferred = new Deferred(); var httpService:HTTPService =new HTTPService(); httpService.url= url; httpService.resultFormat="e4x"; httpService.method = URLRequestMethod.GET; httpService.addEventListener(ResultEvent.RESULT, onSuccess); httpService.addEventListener(FaultEvent.FAULT, onFail); httpService.send(); return deferred.promise; function onSuccess(result:ResultEvent):void { deferred.resolve(result); } function onFail(fault:FaultEvent):void { deferred.reject(fault); } }
调用时能够这样写:
public function process(url:String):void { var p:Promise = httpGet(url); p.then(doSomthing, doError); } public function doSomthing(result:Object):void { } public function doError(result:Object):void { }
最关键的一步就是then方法,当请求成功时,执行doSomthing,失败时执行doError
经过这种方式,异部请求简化到了脑殘的程度,好处有4点
1、不须要全局消息机制,省了一大陀工做量,且没有并发问题;
2、请求方法自己与业务逻辑处理彻底分开,互不干扰,do something的部分彻底能够放在另外的文件中来写。不管是get请求仍是之后的业务逻辑处理方法都是可复用的;
3、请求方法自己直接返回结果,能够同步处理查寻结果。
4、能够链式调用、嵌套调用,想怎么用就怎么用~~~
如今假设业务逻辑要实现一个3次查询的操做,每次查询URL都依赖上一次的查询结果,在没有Promise/Deferred模式以前,只能用3层回调函数嵌套在一直,这简直是恶梦,不过如今简单多了,你能够这样写:
public function process(url:String):void { var p1:Promise = httpGet(url); p1.then(action_1to2).then(action_2to3).then(action3); function action_1to2(result:Object):Promise { var url2:String = getUrl2(result); var p:Promise = httpGet(url2); return p; } function action_2to3(result:Object):Promise { var url3:String = getUrl3(result); var p:Promise = httpGet(url3); return p; } function action3(result:Object):void { // do something
} }
如上,3个get请求是串行的关系,只须要用then链把它们链接起来就能够了,而后本身实现一下getUrl2和getUrl3两个方法就大功告成了。假如此时需求变了,要求交换一下前两次查询的顺序,你也只须要改动不多的代码,爽不爽!我的认为链式调用最有用的一点就是逻辑清晰,在视觉上把每一步要作的工做紧密放在一块儿,一目了然,只要读这一行代码就知道第一部作什么,第二步作什么,第三步作什么,维护也方便,比消息机制的回调函数强了无数倍。
最爽的还不仅如此,假如3个get请求是并行关系,你还能够这样写:
public function process(url1:String, url2:String, url3:String):void { var p1:Promise = httpGet(url1); var p2:Promise = httpGet(url2); var p3:Promise = httpGet(url3); Promise.all([p1, p2, p3]).then(doSomething, doError); } public function doSomething(result:Array):void { var result0:Object = result[0]; var result1:Object = result[1]; var result2:Object = result[2]; // do something
} public function doError(fault:Fault):void { }
当3个请求所有成功时,执行doSomething,只要有一个请求失败,则执行doError。
假设这时需求又变了,要求在查寻过程当中,前端显示一个loading画面,查寻结束后,画面消失,你能够这样简单的改一下代码:
public function process(url1:String, url2:String, url3:String):void { showLoadingImage(); var p1:Promise = httpGet(url1); var p2:Promise = httpGet(url2); var p3:Promise = httpGet(url3); Promise.all([p1, p2, p3]).then(doSomething, doError).always(removeLoadingImage); } function doSomething(result:Array):void { var result0:Object = result[0]; var result1:Object = result[1]; var result2:Object = result[2]; // do something
} function doError(fault:Fault):void { }
always方法的含意是不管前面的协议成功或者失败,都执行下一个方法。在Promise/Deferred模式的状况下,你不用在3次请求的6个回调函数里分别来执行removeLoadingImage方法,只需一次调用便可,是否是很方便呢?