ajax请求的异步嵌套问题分析

(本文章以as3代码为例)

问题的产生

  在前端开发时,经常会使用到Ajax(Asynchronous Javascript And XML)请求向服务器查询信息(get)或交换数据(post),ajax请求都是异步响应的,每次请求都不能同步返回结果,而且多次请求嵌套在一起时,逻辑很难处理,怎么办呢?

  在as3中,get请求的写法通常如下

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方法中,对于查询结果的逻辑处理都要写在查询成功的回调函数内,如果业务逻辑很复杂的话,这种写法就太麻烦了,而且不能复用。

  一种解决思路是通过消息来传递查询结果,当查询成功或失败时,发送相应的消息和结果给该次查询的监听者,代码如下(注意红色加粗部分)

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个缺点:

1、必须有一个全局消息总线来控制所有的消息。当查询次数多时,需要为每次查询定义不同的消息,还要考虑并发时同一个业务请求的消息不能相同,这种全局消息总线对于消息的管理代价太大;

2、action方法仍然不能复用,每次不同的查询都需要重新写一个新的方法;

3、action方法仍然是异部处理,方法本身无法返回查询结果,以致程序的前后语意不连贯。当请求次数多时,对于一个业务逻辑的处理,必须要分开写在很多个回调函数内,这些回调函数彼此之间也无法沟通。

4、最重要的一点是,当一个业务逻辑处理需要多次查询时,每次查询只能嵌套在上一次查询的成功回调函数内,如此,除了最内层的查询可以复用,外内所有的查询方法都不能复用,代码极难维护,这时如果需要修改两个查询的先后顺序,你就惨了。

寻找答案

Promise/Deferred模式  (协议/延时模式)

Promise/Deferred模式最早出现在Javascript的Dojo框架中,它是对异步编程的一种抽象。

  • promise处于且只处于三种状态:未完成,完成,失败。
  • promise的状态只能从未完成转化为完成或失败,不可逆。完成态与失败态之间不能相互转化。
  • promise的状态一旦转化,将不能更改

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方法,只需一次调用即可,是不是很方便呢?

as3的promise库开源下载地址

js的promise开源下载地址(Q.js)

时间: 2024-08-26 19:20:49

ajax请求的异步嵌套问题分析的相关文章

jquey中ajax请求的异步和同步设置

需要实现的效果:点击按钮后,分别调用三个接口更新数据,数据更新完之后刷新当前页面. (实现方式未必是最好的,此处只关注ajax的异步设置) 一般情况下使用 $.ajax() 是异步处理的,三个ajax请求分别发送后,可以在未有response返回的情况下继续执行下面reload()页面的代码 设置 async 为 false 则可以让ajax请求变成同步执行,就是执行完第一个请求后再执行第二请求. 1 function initData(nick, url, accessToken) { 2 $

如何将Ajax请求从异步改为同步

Ajax请求默认的都是异步的 如果想同步 async设置为false就可以(默认是true) var html = $.ajax({   url: "some.PHP",   async: false }).responseText; 或者在全局设置Ajax属性 $.ajaxSetup({   async: false   }); 再用post,get就是同步的了 转载来源:http://blog.csdn.net/xiaoguang44/article/details/6454727

MVC + AJAX请求失败的问题

以前的工作都是后台的开发,对于ajax也是一知半解,今天遇到一个问题折腾了许久,吐血解决.. 前台代码: 后台代码: 问题描述: 用火狐浏览器调试的时候  每次执行到SUCCESS:function(data)就回跳回 33行,用IE10或者chrome浏览器调试的时候 是不会执行$.AJAX(){},直接跳过ajax 解决方案:(吐血了) 服务 -> "ASP.NET状态服务" 开启 搜了许多解决方案,学习了很多 原文地址:http://jingyan.baidu.com/al

使用jQuery形成Ajax请求

1.创建MVC4新项目,使用"Internet应用程序"模板. 2.添加CustomAjax控制器,具体代码如下. public class CustomAjaxController : Controller { public ActionResult Index() { return View(); } public ActionResult PrivacyPolicy() { return PartialView(); } } 3.添加PrivacyPolicy分部视图 @sect

不使用回调函数的ajax请求实现(async和await简化回调函数嵌套)

在常规的服务器端程序设计中, 比如说爬虫程序, 发送http请求的过程会使整个执行过程阻塞,直到http请求响应完成代码才会继续执行, 以php为例子 $url = "http://www.google.com.hk"; $result = file_get_contents($url); echo $result; 当代码执行到第二行时,程序便陷入了等待,直到请求完成,程序才会继续往下跑将抓取到的html输出.这种做法的好处是代码简洁明了,运行流程清晰, 容易维护. 缺点就是程序的运

jQuery基础(Ajax,load(),getJSON(),getScript(),post(),ajax(),同步/异步请求数据)

1.使用load()方法异步请求数据 使用load()方法通过Ajax请求加载服务器中的数据,并把返回的数据放置到指定的元素中,它的调用格式为: load(url,[data],[callback]) 参数url为加载服务器地址,可选项data参数为请求时发送的数据,callback参数为数据请求成功后,执行的回调函数. 2.使用getJSON()方法异步加载JSON格式数据 使用getJSON()方法可以通过Ajax异步请求的方式,获取服务器中的数据,并对获取的数据进行解析,显示在页面中,它的

jQuery框架+DWR框架实现的Java Web中的Ajax效果(异步请求,局部刷新)

一 简介和实现效果 这里用一个小例子来简单举例说明,做一个搜索引擎搜索提示效果,通过不断输入字符,然后在下方给出搜索提示.效果图如下: 通过上图可以看到,当输入一个"a"时,提示了很多内容,然后继续输入一个"e"后,提示的范围明显就变小了. 注:在文末我会给出完整源代码的下载链接,以供大家参考 二 具体实现 1 在eclipse for java ee中创建一个Java Web工程,然后导入相应的jar包,特别说明的是:这里要导入一个额外的dwr.jar.也就是说,

详解Ajax请求(四)——多个异步请求的执行顺序

首先提出一个问题:点击页面上一个按钮发送两个ajax请求,其中一个请求会不会等待另一个请求执行完毕之后再执行? 答案是:不会,这两个异步请求会同时发送,至于执行的快与慢,要看响应的数据量的大小及后台逻辑的复杂程度. 从异步请求的执行原理来看,我们知道当一个异步请求发送时,浏览器不会处于锁死.等待的状态,从一个异步请求发送到获取响应结果的期间,浏览器还可以进行其它的操作.这就意味着多个异步请求的执行时并行的. 下面我们还是从一个例子来看一下这个问题. 要求:ajax1从后台请求下拉列表的数据,aj

ajax,同步请求 ,异步请求

同步请求,异步请求 ,这样的字眼我想既然你能看到这篇文章并且点击进来 ,说明我们应该是听过他的,并且 听得次数还不少,说到异步请求 ,首先我想到就是ajax, ajax的问题一会再说,今天我突然想到,我们平时都会 尽量的去 用ajax使用异步 的方式请求数据, 那么你知道到底是同步请求的数据更快,还是异步请求的数据更快吗? 异步传输是面向字符的传输,它的单位是字符:而同步传输是面向比特的传输,它的单位是桢,它传输的时候要求接受方和发送方的时钟是保持一致的. 同步传输通常要比异步传输快速得多.接收