用Dojo实现Ajax请求:XHR、跨域、及其他

在任何浏览器上方便地实现Ajax请求是每一个Ajax框架的初衷。Dojo在这方面无疑提供了非常丰富的支持。除了XMLHttpRequest之外,动态script、iframe、RPC也应有尽有,并且接口统一,使用方便,大多数情况下都只需要一句话就能达到目的,从而免除重复造轮子的麻烦。

AD: <script src="http://www.51cto.com/js/article/keywords_ad_new.js"></script>

在任何浏览器上方便地实现Ajax请求是每一个Ajax框架的初衷。Dojo在这方面无疑提供了非常丰富的支持。除了XMLHttpRequest之外,动态script、iframe、RPC也应有尽有,并且接口统一,使用方便,大多数情况下都只需要一句话就能达到目的,从而免除重复造轮子的麻烦。而且,Dojo一贯追求的概念完整性也在这里有所体现,换句话说,在使用Dojo的Ajax工具的过程中不会感到任何的不自然,相反更容易有触类旁通的感觉,因为API的模式是统一的,而且这里涉及到的某些概念(如Deferred对象)也贯穿在整个Dojo之中。

Dojo的XHR函数

Dojo的XMLHttpRequest函数就叫dojo.xhr,除了把自己取名美元符号之外,这好像是最直接的办法了。它定义在Dojo基本库里,所以不需要额外的require就能使用。它可以实现任何同域内的http请求。不过更常用的是dojo.xhrGet和dojo.xhrPost,它们只不过是对dojo.xhr函数的简单封装;当然根据REST风格,还有dojo.xhrPut和dojo.xhrDelete。

这些函数的参数都很统一。除了dojo.xhr的第一个参数是http方法名之外,所有的dojo.xhr*系列函数都接受同一种散列式的参数,其中包含请求的细节,例如url、是否同步、要传给服务器的内容(可以是普通对象、表单、或者纯文本)、超时设定、返回结果的类型(非常丰富且可扩展)、以及请求成功和失败时的回调。所有dojo.xhr*函数(实际上是所有IO函数)返回值也都一样,都是一个Deferred对象,顾名思义,它能让一些事情“延迟”发生,从而让API用起来更灵活。

下面的两个例子可能会带来一点直观感受:

   
   
  1. dojo.xhrGet({
  2. url:"something.html",
  3. load:function(response,ioArgs){
  4. //用response干一些事
  5. console.log("xhrgetsuccess:",response);
  6. returnresponse;//必须返回response
  7. },
  8. error:function(response,ioArgs){
  9. console.log("xhrgetfailed:",response);
  10. returnresponse;//必须返回response
  11. }
  12. });
  13. //Deferred对象允许用同步调用的写法写异步调用
  14. vardeferredResult=dojo.xhrPost({
  15. url:"something.html",
  16. form:formNode,//Dojo会自动将form转成object
  17. timeout:3000,//Dojo会保证超时设定的有效性
  18. handleAs:"json"//得到的response将被认为是JSON,并自动转为object
  19. });
  20. //当响应结果可用时再调用回调函数
  21. deferredResult.then(function(response){
  22. console.log("xhrgetsuccess:",response);
  23. returnresponse;//必须返回response
  24. });

首先解释一下timeout。除了IE8之外,目前大多数XMLHttpRequest对象都没有内置的timeout功能,因此必须用 setTimeout。当同时存在大量请求时,需要为每一个请求设置单独的定时器,这在某些浏览器(主要是IE)会造成严重的性能问题。dojo的做法是只用一个单独的setInterval,定时轮询(间隔50ms)所有还未结束的请求的状态,这样就高效地解决了一切远程请求(包括JSONP和 iframe)的超时问题。

值得一提的还有handleAs参数,通过设置这个参数,可以自动识别服务器的响应内容格式并转换成对象或文本等方便使用的形式。根据文档,它接受如下值:text (默认),json,json-comment-optional,json-comment-filtered,javascript,xml。

而且它还是可扩展的。其实handleAs只是告诉xhr函数去调用哪个格式转换插件,即dojo.contentHandlers对象里的一个方法。例如 dojo.contentHandlers.json就是处理JSON格式的插件。你可以方便地定制自己所需要的格式转换插件,当然,你也可修改现有插件的行为:

   
   
  1. dojo.contentHandlers.json=(function(old){
  2. returnfunction(xhr){
  3. varjson=old(xhr);
  4. if(json.someSignalFormServer){
  5. doSomthing(json);
  6. deletejson.someSignalFormServer;
  7. }
  8. returnjson;
  9. }
  10. })(dojo.contentHandlers.json);//一个小技巧,利用传参得到原方法

虚拟的参数类

这里特别提一下Dojo在API设计上的两个特点。其一是虚拟的参数“类”概念:通过利用javascript对象可以灵活扩展的特点,强行规定一个散列参数属于某个“类”。例如dojo.xhr*系列函数所接受的参数就称为dojo.__XhrArgs。这个“类”并不存在于实际代码中(不要试图用 instanceof验证它),只停留在概念上,比抽象类还抽象,因此给它加上双下划线前缀(Dojo习惯为抽象类加单下划线前缀)。这样做看起来没什么意思,但实际上简化了API,因为它使API之间产生了联系,更容易记忆也就更易于使用。这一点在对这种类做“继承”时更明显。例如 dojo.__XhrArgs继承自dojo.__IoArgs,这是所有IO函数所必须支持的参数集合,同样继承自dojo.__IoArgs的还有 dojo.io.script.__ioArgs和dojo.io.iframe.__ioArgs,分别用于动态脚本请求和iframe请求。子类只向父类添加少量的属性,这样繁多的参数就具有了树形类结构。原本散列式参数是用精确的参数名代替了固定的参数顺序,在增加灵活性和可扩展性的同时,实际上增加了记忆量(毕竟参数名不能拼错),使得API都不像看起来那么好用,有了参数类的设计就缓解了这个问题。

这种参数类的做法在Dojo里随处可见,读源码的话就会发现它们都是被正儿八经地以正常代码形式声明在一种特殊注释格式里的,像这样:

   
   
  1. /*=====
  2. dojo.declare("dojo.__XhrArgs",dojo.__IoArgs,{
  3. constructor:function(){
  4. //summary:
  5. //...
  6. //handleAs:
  7. //...
  8. //......
  9. }
  10. });
  11. =====*/

这种格式可以被jsDoc工具自动提取成文档,在文档里这些虚拟出来的类就像真的类一样五脏俱全了。

Deferred对象

另一个API设计特点就是Deferred对象的广泛使用。Dojo里的Deferred是基于MochiKit实现稍加改进而成的,而后者则是受到 python的事件驱动网络工具包Twisted里同名概念的启发。概括来说的话,这个对象的作用就是将异步IO中回调函数的声明位置与调用位置分离,这样在一个异步IO最终完成的地方,开发人员可以简单地说“货已经到了,想用的可以来拿了”,而不用具体地指出到底该调用哪些回调函数。这样做的好处是让异步IO的写法和同步IO一样(对数据的处理总是在取数据函数的外面,而不是里面),从而简化异步编程。

具体做法是,异步函数总是同步地返回一个代理对象(这就是Deferred对象),可以将它看做你想要的数据的代表,它提供一些方法以添加回调函数,当数据可用时,这些回调函数(可以由很多个)便会按照添加顺序依次执行。如果在取数据过程中出现错误,就会调用所提供的错误处理函数(也可以有很多个);如果想要取消这个异步请求,也可通过Deferred对象的cancel方法完成。

dojo.Deferred的核心方法如下:

   
   
  1. then(callback,errback);//添加回调函数
  2. callback(result);//表示异步调用成功完成,触发回调函数
  3. errback(error);//表示异步调用中产生错误,触发错误处理函数
  4. cancel();//取消异步调用

Dojo还提供了一个when方法,使同步的值和异步的Deferred对象在使用时写法一样。例如:

   
   
  1. //某个工具函数的实现
  2. varobj={
  3. getItem:function(){
  4. if(this.item){
  5. returnthis.item;//这里同步地返回数据
  6. }else{
  7. returndojo.xhrGet({//这里返回的是Deferred对象
  8. url:"toGetItem.html",
  9. load:dojo.hitch(this,function(response){
  10. this.item=response;
  11. returnresponse;
  12. })
  13. });
  14. }
  15. }
  16. };
  17. //用户代码
  18. dojo.when(obj.getItem(),function(item){
  19. //无论同步异步,使用工具函数getItem的方式都一样
  20. });

在函数闭包的帮助下,Deferred对象的创建和使用变得更为简单,你可以轻易写出一个创建Deferred对象的函数,以同步的写法做异步的事。例如写一个使用store获取数据的函数:

   
   
  1. varstore=newdojo.data.QueryReadStore({...});
  2. functiongetData(start,count){
  3. vard=newdojo.Deferred();//初始化一个Deferred对象
  4. store.fetch({
  5. start:start,
  6. count:count,
  7. onComplete:function(items){
  8. //直接取用上层闭包里的Deferred对象
  9. d.callback(items);
  10. }
  11. });
  12. returnd;//把它当做结果返回
  13. }

用dojo.io.script跨域

dojo.xhr* 只是XmlHttpRequest对象的封装,由于同源策略限制,它不能发跨域请求,要跨域还是需要动态创建<script>标签。Dojo 没有像JQuery一样把所有东西都封装在一起(JQuery的ajax()方法可以跨域,当然用的是JSONP,所以它不敢把自己称为xhr),而是坚持一个API只干一件事情。毕竟在大部分应用中,同域请求比跨域请求多得多,如果一个应用不需要跨域,就没必要加载相关代码。因此与xhr不同,dojo 的跨域请求组件不在基本库,而在核心库,需要require一下才能使用:

   
   
  1. dojo.require("dojo.io.script");

这个包里面基本上只需要用到一个函数:dojo.io.script.get()。它也返回Deferred对象,并接受类型为 dojo.io.script.__ioArgs的散列参数。受益于虚拟参数类,我们不用从头开始学习这个参数,它继承了dojo.__IoArgs,因此和dojo.xhr*系列的参数大同小异。唯一需要注意的是handleAs在这里无效了,代之以jsonp或者checkString。

前者用于实现JSONP协议,其值由服务器端指定,当script标签加载后就按照JSONP协议执行这个函数,然后Dojo会自动介入,负责把真正的数据传给load函数。需要指出的是在Dojo1.4以前,这个参数叫callbackParamName,冗长但意义明确。毕竟Dojo太早了,它成型的时候(2005)JSONP这个词才刚出现不久。现在callbackParamName还是可用的(为了向后兼容),不过处于deprecated状态。

下面的例子从flickr获取feed数据:

   
   
  1. dojo.io.script.get({
  2. url:"http://www.flickr.com/services/feeds/photos_public.gne",
  3. jsonp:"jsoncallback",//由flickr指定
  4. content:{format:"json"},
  5. load:function(response){
  6. console.log(response);
  7. returnresponse;
  8. },
  9. error:function(response){
  10. console.log(response);
  11. returnresponse;
  12. }
  13. });

与jsonp不同,checkString参数专门用于跨域获取javascript代码,它其实是那段跨域脚本里的一个有定义的变量的名字,Dojo会用它来判断跨域代码是否加载完毕,配合前面提到的timeout机制就能实现有效的超时处理。

   
   
  1. dojo.io.script.get({
  2. url:"http://......",//某个提供脚本的URL
  3. checkString:"obj",
  4. load:function(response){
  5. //脚本加载完毕,可以直接使用其中的对象了,如obj。
  6. Returnresponse;
  7. }
  8. });

用dojo.io.iframe传数据

dojo.io 包里还有一个工具就是iframe,常用于以不刷新页面的方式上传或下载文件。这个很经典的Ajax技巧在Dojo里就是一句 dojo.io.iframe.send({...})。这个函数接受dojo.io.iframe.__ioArgs,相比 dojo.__IoArgs,它只多了一个method参数,用于指定是用GET还是POST(默认)方法发送请求。下面的例子就实现了通过无刷新提交表单来上传文件:

   
   
  1. dojo.io.iframe.send({
  2. form:"formNodeId",//某个form元素包含本地文件路径
  3. handleAs:"html",//服务器将返回html页面
  4. load:onSubmitted,//提交成功
  5. error:onSubmitError//提交失败
  6. });

目前send函数的handleAs参数支持html,xml,text,和javascript五种响应格式。除了html和xml之外,使用其他格式有一个比较特别的要求,就是服务端返回的响应必须具有以下格式:

   
   
  1. <html>
  2. <head></head>
  3. <body>
  4. <textarea>真正的响应内容</textarea>
  5. </body>
  6. </html>

这是因为服务器返回的东西是加载在iframe里的,而只有html页面才能在任何浏览器里保证成功加载(有个DOM在,以后取数据也方便)。加一个<textarea>则可以尽量忠实于原始文本数据的格式,而不会受到html的影响。

试试RPC(远程过程调用)

如果dojo.xhr*函数以及Deferred机制仍然无法避免代码的混乱,那RPC可能就是唯一的选择了。dojo.rpc包提供了基于“简单方法描述”语言(SMD)的RPC实现。SMD的原理类似于WSDL(Web服务描述语言),不过是基于JSON的,它定义了远程方法的名称、参数等属性,让 Dojo能创建出代理方法以供调用。

Dojo提供了两种方式实现rpc:XHR和JSONP,分别对应dojo.rpc.JsonService类和dojo.rpc.JsonpService类,用户可以根据是否需要跨域各取所需。

一个简单的例子:

   
   
  1. varsmdObj={
  2. serviceType:"JSON-RPC",
  3. serviceURL:"http://...."
  4. methods:[
  5. {name:"myFunc",parameters:[]}
  6. ]
  7. };
  8. varrpc=newdojo.rpc.JsonpService(smdObj);//传入SMD
  9. varresult=rpc.myFunc();//直接调用远程方法,返回Deferred对象
  10. dojo.when(result,function(result){
  11. //得到结果
  12. });

SMD还没有一个被广泛认可的官方标准,一直以来都是Dojo社区领导着它的发展,以后这个模块也有可能发生改变,但整个RPC基本的框架会保持稳定。

结语

Ajax请求这个主题太大,本文只能挂一漏万地介绍一点dojo在这方面设计和实现的皮毛,包括基本XHR请求、动态script、iframe请求、以及RPC,并特别强调了几个有Dojo特色的设计,如timeout机制、虚拟参数类、Deferred对象等。

Dojo由Ajax领域的先驱们写就,相信从它的代码中我们一定能学到更多的东西。

原文链接:http://www.infoq.com/cn/articles/dojo-ajax-xhr

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


我有一个网格,可以根据更大的树结构编辑小块数据.为了更容易知道用户保存了什么,我希望当用户第一次看到网格时,网格处于不可编辑状态.当用户准备好后,他们可以单击编辑按钮,这将使网格的某些部分可编辑.然后,有一个保存或取消按钮可以保存更改或还原.在大多数情况下它是有效的.但
我即将开始开发一款教育性的视频游戏.我已经决定以一种我可以轻松打包为Web,Mobiles和可能的Standalone版本的方式来实现这一目标.我不想使用Flash.因此,我确信(无论如何我会听取建议)使用JavaScript和SVG.我正在对这个问题进行大量研究,但我很难把各个部分放在一起.我知道Raphae
我正在使用带有Grails2.3.9的Dojo1.9.DojoNumberTextBox小部件–我在表单中使用–将固定格式(JavaScript基本格式)的实数值(例如:12.56)设置为HTML表单输入字段(但根据浏览器区域设置显示/编辑它们,所以用户总是看到格式正确的数字).另一方面,Grails期望输入字段根据浏览器
1.引言鉴于个人需求的转变,本系列将记录自学arcgisapiforjavaScript的学习历程,本篇将从最开始的arcgisapiforjavaScript部署开始,个人声明:博文不在传道受业解惑,旨在方便日后复习查阅。由于是自学,文章中可能会出现一些纰漏,请留言指出,不必留有情面哦!2.下载ArcGISforDe
我正在阅读使用dojo’sdeclare进行类创建的语法.描述令人困惑:Thedeclarefunctionisdefinedinthedojo/_base/declaremodule.declareacceptsthreearguments:className,superClass,andproperties.ClassNameTheclassNameargumentrepresentsthenameofthec
我的团队由更多的java人员和JavaScript经验丰富组成.我知道这个问题曾多次被问到,但为了弄清楚我的事实,我需要澄清一些事情,因为我在客户端技术方面的经验非常有限.我们决定使用GWT而不是纯JavaScript框架构建我们的解决方案(假设有更多的Java经验).这些是支持我的决定的事实.>
路由dojo/framework/srcouting/README.mdcommitb682b06ace25eea86d190e56dd81042565b35ed1Dojo应用程序的路由路由FeaturesRoute配置路径参数RouterHistoryManagersHashHistoryStateHistoryMemoryHistoryOutletEventRouterContextInjectionOutl
请原谅我的无知,因为我对jquery并不熟悉.是否有dojo.connect()的等价物?我找到了这个解决方案:http:/hink-robot.com/2009/06/hitch-object-oriented-event-handlers-with-jquery/但是没有断开功能!你知道jquery的其他解决方案吗?有jquery.connect但这个插件在我的测试中不起作用.
与java类一样,在dojo里也可以定义constructor 构造函数,在创建一个实例时可以对需要的属性进行初始化。//定义一个类mqsy_yjvar mqsy_yj=declare(null,{     //thedefaultusername    username: "yanjun",          //theconstructor   
我一直在寻找一些最佳实践,并想知道Dojo是否具有框架特定的最佳实践,还是最好只使用通用的Javascript标准?特别是我主要是寻找一些功能和类评论的指导方针?解决方法:对于初学者来说,这是项目的风格指南:DojoStyleGuide
我有’05/17/2010’的价值我想通过使用dojo.date.locale将其作为“2010年5月17日”.我尝试过使用dojo.date.locale.parse,如下所示:x='05/17/2010'varx=dojo.date.locale.parse(x,{datePattern:"MM/dd/yyyy",selector:"date"});alert(x)这并没有给我所需的日期
我正在尝试创建一个包含函数的dojo类,这些函数又调用此类中的其他函数,如下所示:dojo.provide("my.drawing");dojo.declare("my.drawing",null,{constructor:function(/*Object*/args){dojo.safeMixin(this,args);this.container=args[0];
我知道你可以使用jQuery.noConflict为jQuery做这件事.有没有办法与Dojo做类似的事情?解决方法:我相信你可以.有关在页面上运行多个版本的Dojo,请参阅thispage.它很繁琐,但似乎是你正在寻找的东西.一般来说,Dojo和jQuery都非常小心,不会破坏彼此或其他任何人的变量名.
我有一个EnhancedGrid,用户经常使用复杂的过滤器.有没有办法允许用户保存或标记过滤器,以便将来可以轻松地重新应用它?我知道我可以通过编程方式设置过滤器,但我无法预测用户想要的过滤器.谢谢!编辑:自己做了一些进展…使用grid.getFilter()返回过滤器的JSON表示,然后使用json.strin
我有这个代码:dojo.declare("City",null,{constructor:function(cityid,cityinfo){}});dojo.declare("TPolyline",GPolyline,{constructor:function(points,color){},initialize:function(map){});应该是什
我遇到的问题是我的所有javascript错误似乎来自dojo.xd.js或子模块.我正在使用chrome调试器和许多dijit功能,如dijit.declaration和dojo.parser.这有点烦人,因为它很难找到简单的错误或滑倒.我希望我可以添加一个选项,允许我的调试器在我的非dojo代码中显示选项会发生的位置.我是
我正在使用DojoToolkit数字/解析函数来处理格式化和使用ICU模式语法解析字符串.有没有人知道有可能采取任意ICU模式字符串并以某种方式使用Dojo(或其他)库将其分解为它的部分(例如,对于数字模式,它可以被分解为小数位数,数千个分组等…).我希望这样做,而不需要让我的代码密切了
我有两个看似相关的问题,访问在不同的地方定义的javascript函数.我遇到的第一个问题是调用我在firgbug或safari控制台中定义的函数.我定义了一个名为getRed的函数,如下所示:functiongetRed(row,col){//dosomethingstuffandreturntheredvalueasa
我想添加一个在Ajax调用中指定的外部样式表.我已经找到了一种方法来使用jQuery(参见下面的示例),但是我需要使该方法适应dojoJavaScript框架.JQuery示例$('head').append('<linkrel="stylesheet"type="text/css"href="lightbox_stylesheet.css">');谢谢.解决方法:一旦你
我正在尝试使用dojo.connect将onMouseDown事件连接到图像,如:dojo.connect(dojo.byId("workpic"),"onMouseDown",workpicDown);functionworkpicDown(){alert("mousedown");}类似的代码几行后,我将onMouse*事件连接到dojo.body确实完全正常工作.但是当我点击图像时