zoukankan      html  css  js  c++  java
  • 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模式可以把异部处理用同步的形式表达出来,极大方便了代码维护,语意更加清晰,也方便了代码复用。

    这里是两个开源地址: 

    as3的promise库开源下载地址

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

    尝试

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

     

  • 相关阅读:
    Poj 2017 Speed Limit(水题)
    Poj 1316 Self Numbers(水题)
    Poj 1017 Packets(贪心策略)
    Poj 1017 Packets(贪心策略)
    Poj 2662,2909 Goldbach's Conjecture (素数判定)
    Poj 2662,2909 Goldbach's Conjecture (素数判定)
    poj 2388 Who's in the Middle(快速排序求中位数)
    poj 2388 Who's in the Middle(快速排序求中位数)
    poj 2000 Gold Coins(水题)
    poj 2000 Gold Coins(水题)
  • 原文地址:https://www.cnblogs.com/easymind223/p/4086427.html
Copyright © 2011-2022 走看看