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

时间:2021-11-22 10:26:32

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

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

  1. ExtJS4 ajax请求同步异步问题

    今天在写代码过程中遇到一个奇怪的问题.事情是这种,我写了一个简单的页面用来删除选中的用户,请看以下: 由于后台的servlet的代码实现了依据用户名来删除一条记录.所以我在前台的ExtJS代码里面用了 ...

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

    Ajax请求默认的都是异步的 如果想同步 async设置为false就可以(默认是true) var html = $.ajax({   url: "some.PHP",   as ...

  3. Python爬虫连载9-JS加密之“盐”​、ajax请求

    一.JS加密之“盐”​ 1.salt属性“盐":多用于密码学,比如我们的银行卡是六位密码,但是实际上在银行的系统里,我们输入密码后,会给原始的密码添加若干字符,形成更加难以破解的密码.这个过 ...

  4. 详解Ajax请求(二)——异步请求原理的分析

    在上一文章里,我们分析了同步请求的原理.当浏览器向服务器发送同步请求时,服务处理同步请求的过程中,浏览器会处于等待的状态,服务器处理完请求把数据响应给浏览器并覆盖浏览器内存中原有的数据,浏览器重新加载 ...

  5. 用Promise解决多个异步Ajax请求导致的代码嵌套问题【转】

    问题 前端小同学在做页面的时候,犯了个常见的错误:把多个Ajax请求顺序着写下来了,而后面的请求,对前面请求的返回结果,是有依赖的.如下面的代码所示: var someData; $.ajax({ u ...

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

    在常规的服务器端程序设计中, 比如说爬虫程序, 发送http请求的过程会使整个执行过程阻塞,直到http请求响应完成代码才会继续执行, 以php为例子 $url = "http://www. ...

  7. 嵌套的 ajax 请求

    今天看到了一个嵌套的ajax,算是长见识了. 不过看一眼就知道怎么回事了. 不是不会,就怕想不到. //发送一个ajax请求,嵌套的ajax请求 'ajaxCall': function(url1, ...

  8. 分析ajax请求过程以及请求方法

    ajax 的全称是Asynchronous JavaScript and XML,其中,Asynchronous 是异步的意思,它有别于传统web开发中采用的同步的方式.据小编*了解到,ajax很早 ...

  9. AJAX请求详解 同步异步 GET和POST

    AJAX请求详解 同步异步 GET和POST 上一篇博文(http://www.cnblogs.com/mengdd/p/4191941.html)介绍了AJAX的概念和基本使用,附有一个小例子,下面 ...

随机推荐

  1. MHA+Atlas+mysql一主一从开启gtid安装配置与实验

    各节点架构 (说明:生产环境有两个节点可以组成一套完整集群,我是测试环境,因此对于manager以及atlas和binlog server都是单点,如果生产环境,相应的将manager以及atlas和 ...

  2. 你一定能用的上的iOS第三方库

    点国内程序员不常用的热门iOS第三方库:看完,还敢自称"精通iOS开发"吗? 综合github上各个项目的关注度与具体使用情况,涵盖功能,UI,数据库,自动化测试,编程工具等类型, ...

  3. Ambry: LinkedIn’s Scalable Geo-Distributed Object Store

    https://github.com/linkedin/ambry http://www.open-open.com/lib/view/open1464828607502.html

  4. 检测鼠标键盘多久没有活动(使用GetLastInputInfo API函数检测)

    DELPHI代码 unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Contro ...

  5. Java之SPI机制

    之前开阿里的HSF框架,里面用到了Java的SPI机制,今天闲暇的时候去了解了一下,通过写博客来记录一下 SPI的全名为Service Provider Interface,我对于该机制的理解是为接口 ...

  6. centos7内核升级

    默认centos7的内核版本是3.10,升级的原因是为了测试openvswitch的vlan技术,默认openvswitch的2.3版本是允许centos7默认内核3.10支持,下面是软件与内核版本对 ...

  7. Mac下安装SecureCRT并激活

    今天花了好长的时间终于把SecureCRT安装成功了 现在分享给大家 安装的步骤, 希望对大家用帮助 Mac下的SecureCRT需要破解才能使用 所以有些费劲的.. 先下载SecureCRT和破解文 ...

  8. flask使用原生ajax、不使用表单(Form)上传文件

    〇.知识点 jquery ajax 文档告诉你可以使用默认的 application/x-www-form-urlencoded, multipart/form-data, or text/plain ...

  9. js动画(速度)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta ht ...

  10. js判断文本是否溢出容器

    onShowNameTipsMouseenter: function(e) { var target = e.target; var containerLength = $(target).width ...