深入浅出dojo/request

难度:中等
Dojo版本:1.7
原作者:Bryan Forbes
译者:Oliver (zhuxw1984@gmail.com)

随着Dojo向着2.0大步迈进,我们已开始致力于为开发人员提供能在任何JavaScript环境下保持高效生产力的工具。这意味着我们所创建的API必须在所有环境下都保持一致。从这个角度看,有一个领域的API总是被遗漏,那就是Dojo的IO函数。我们已经为开发人员提供了在浏览器中发起请求的方法(dojo.xhr*,dojo.io.iframe,dojo.io.script),但有些人不太喜欢这些API所表现出的不一致性(例如dojo.xhrGet以及dojo.io.script.get,等等)。另外,我们还从来没有提供一套服务器端的实现;就算提供了,也肯定是另一套不同模块和API,大家就又需要记忆更多东西了。

在Dojo1.8发布之际,我们隆重推出dojo/requestAPI。这套API在所有的浏览器、所有请求方法、甚至所有JavaScript环境上都是一致的:
require(["dojo/request"],function(request){
    var promise = request(url,options);
    promise.then(
        function(data){
        },function(error){
        }
    );
    promise.response.then(
        function(response){
        },function(error){
        }
    );
    request.get(url,options).then(...);
    request.post(url,options).then(...);
    request.put(url,options).then(...);
    request.del(url,options).then(...);
});
dojo/request函数(以及该模块下所有的发起请求的函数)的签名包含一个URL以及一个选项对象。这个选项对象中可以配置有关这次请求的各种参数。通常情况下使用dojo/request非常简单,只需要传递一个字符串,option参数是可省略的。让我们来看看option对象中的常用配置参数:
  • method: 用于本请求的HTTP方法(默认是GET,dojo/request/script会忽略这个参数)
  • query: 形如key=value的字符串,或者形如{key: 'value'}的对象,包含所有的query参数
  • data: 字符串或对象(会被dojo/io-query.objectToQuery串行化成字符串),表示需要发送的数据(GET和DELET请求会忽略这个参数)
  • handleAs: 表示如何处理服务器端响应的字符串,默认"text",其他可能的值包括'json','javascript',以及'xml'
  • headers: 形如{'Header-Name': 'value'}的对象,包含请求所需要的各种头部属性
  • timeout: 表示等待多少毫秒算超时的整数,一旦超时将取消请求并"拒绝(reject)"所返回的promise。
API的一致性还包括返回值:所有dojo/request方法都返回一个“保证(promise)”对象,这个promise对象最终会提供响应数据。如果在发起请求时指定了某个响应内容解析器(通过handleAs参数),那么这个promise对象就会提供这个内容解析器的解析结果,否则它将直接返回响应的body部分的文本。

dojo/request所返回的promise对象具有一个普通promise没有的附加属性:response。这个属性本身也是一个promise,它将提供一个对象来更详细地描述这次响应:
  • url: 发起请求的最终URL(加上了query字符串)
  • options: 请求相关的参数
  • text: 响应中数据的字符串表示
  • data: 对响应进行处理后返回的数据(如果handles参数指定了有效的解析方式)
  • getHeader(headerName): 用于获取请求头部参数的函数;如果某个provider没有提供头部信息,这个函数将返回null。


Provider(提供者,这里指能够提供某种请求处理方式的模块 ——译注)

在幕后,dojo/request是通过provider来发起请求的。对于每一个平台dojo都选好了一个合适的默认provider:浏览器使用dojo/request/xhr,Node.js使用dojo/request/node。需要指出的是,比较新的浏览器(IE9+,FF3.5+,Chrome7+,Safari4+)将使用心得XMLHttpRequest2事件,而不是XMLHttpRequest的onreadystatechange,那只有在更老的浏览器中才会使用。另外,Node.js的provider直接使用了http和https模块,这意味着不用在服务器端部署任何接受XMLHttpRequest请求的中间层。

如果需要使用一个非默认的provider(例如JSON-P的provider),有如下三种选择:直接使用非默认provider;把它配置成默认provider;或者配置请求的注册信息。

由于所有的provider都遵循dojo/request API,非默认的provider是可以直接使用的。dojo/request的架构设计思想类似于dojo/store。这意味着如果你只有一些JSON-P服务,你可以直接使用dojo/request/script而不用改变基本的API签名。与其他两种方式比较起来,这种使用非默认provider的方式灵活性较差,但它是的确是一种完全有效的方式。

另一种使用非默认provider的方式是将它配置成默认provider。如果我们知道我们的应用只会使用这一个provider,那这样做就非常有帮助。配置默认provider其实非常简单,就是把provider的模块ID设置成dojoConfig的requestProvider属性:
<script>
    var dojoConfig = {
        requestProvider: "dojo/request/script"
    };
</script>
<script src="path/to/dojo/dojo.js"></script>
requestProvider也可以通过data-dojo-config来设置,就像其他配置参数一样。另外,因为任何遵循dojo/request API的函数都能作为默认provider,我们当然也可以开发一个自定义的模块,将dojo/request/xhr包装起来,再加上额外的头部信息(例如用于身份验证),这样就能作为一个自定义的provider来给我们的应用使用了。而且,在测试阶段,我们也可以使用一个特殊的provider来模拟服务器发回的响应,这样就能验证我们的应用是否在发出正确的请求。

虽然比起直接使用非默认provider来,将其配置成默认能为我们提供更大的灵活性,但这么做仍然不能只通过一个API(dojo/request)就做到根据预设条件来自动使用不同provider的能力。假设我们的应用有一些数据服务,其中一个服务需要一组用于身份验证的头部信息,而另一个需要完全不同的另一组头部信息。或者一个需要JSON-P而另一个需要XMLHttpRequest。这种情况下就是dojo/request/registry闪亮登场的时候了。

注册机制

Dojox中有一个存在了很久但并没有得到广泛使用的模块dojox/io/xhrPlugins。这个模块可以让dojo.xhr*成为所有请求的接口,无论这些请求是通过JSONP发送还是iframe,甚至是其他用户自定义的方式。这个统一接口的思想非常有用,因此被沿用到dojo/request/registry中。

dojo/request/registry同样遵循dojo/request API(因此它本身就可以作为一个provider),不过增加了一个register函数:
// 如果一个请求的URL是"some/url",provider就会被用来处理这个请求
registry.register("some/url",provider);
 
// 如果一个请求的URL以"some/url"开始,provider就会被用来处理这个请求
registry.register(/^some\/url/,provider);
 
// 如果一个请求是一HTTP GET方法发送的,provider就会被用来处理这个请求
registry.register(
    function(url,options){
        return options.method === "GET";
    },provider
);
 
// 如果不能匹配到任何已注册的条件,默认provider将被使用
如果所有条件都不满足,而且也没有备用provider可用,那么当前环境的默认provider将被启用。由于dojo/request/registry符合dojo/request API,它可以作为默认provider:
<script>
    var dojoConfig = {
        requestProvider: "dojo/request/registry"
    };
</script>
<script src="path/to/dojo/dojo.js"></script>
<script>
    require(["dojo/request","dojo/request/script"],function(request,script){
            request.register(/^\/jsonp\//,script);
            ...
        }
    );
</script>
如果我们想要使用平台默认的provider(对于浏览器来说是XHR)作为备用,那这样做当然很好,但我们也可以通过上例中的最后一条语句设置自己的备用provider,只是在这句话之后就不能再注册其他provider了。设置在requestProvider参数上的dojo/request/registry还可以接受插件作为备用provider:
<script>
    var dojoConfig = {
        requestProvider: "dojo/request/registry!my/authProvider"
    };
</script>
<script src="path/to/dojo/dojo.js"></script>
<script>
    require(["dojo/request",script);
            ...
        }
    );
</script>
好了,现在任何不满足预设条件的请求都会由my/authProvider来处理。

注册provide功能的强大可能还不是那么明显。现在让我们来看几个让注册功能大显身手的场景。首先,考虑一个应用,它的服务器端API是在不断变化的。也就是说虽然我们知道每一个具体的终端服务,但我们不知道会需要什么样的头部信息,甚至也不知道响应中会返回什么样的JSON对象。我们可以很容易地为每一项服务注册一个临时provider,然后立马开始开发用户界面。假设我们猜想/service1将在items属性中返回JSON格式的数据项,而/service2将在data属性中返回这些数据项:
request.register(/^\/service1\//,function(url,options){
    var promise = xhr(url,lang.delegate(options,{ handleAs: "json" })),dataPromise = promise.then(function(data){
            return data.items;
        });
    return lang.delegate(dataPromise,{
        response: promise.response
    });
});
request.register(/^\/service2\//,dataPromise = promise.then(function(data){
            return data.data;
        });
    return lang.delegate(dataPromise,{
        response: promise.response
    });
});
现在所有在用户界面中用到的服务请求都可以通过request(url,options).then(...)的形式来使用,并且它们都会接收到正确的数据。随着开发过程的进行,某个服务端团队可能决定改让/service1在data属性中以JSON格式返回数据项,而/service2则以XML的格式返回。如果不使用注册机制,这将导致大量的代码改动。有了注册机制,我们已经将我们的widget和store所需要的接口和服务所能提供的接口解耦了,这意味着服务器端团队的决定只会导致两处代码改变:在我们的两个provider中。理论上,我们甚至能将用户界面与URL进行进一步解耦,通过使用通用的URL让我们的注册机制自动将正确的服务终端映射到正确的provider上。这就避免了服务终端的改动导致的前端代码的大量修改。

这种解耦也可以拓展到测试上。通常在单元测试中无法获得远程服务:远程数据可能变化,远程服务器也可能不可用。这也是为什么推荐使用静态数据做测试。但如果我们的widget和用户界面把服务终端和对应请求都写死在里面了,我们还怎么去测试呢?而如果我们使用了dojo/request/registry,只需要注册一个专门为测试任务返回静态数据的provider就行了,所有的API调用都不需要修改,现有应用中不需要重写任何代码。

结论

可见,dojo/request是为开发者编写的:对于简单的场景有简单的API,对于复杂场景也有非常灵活的选项。

相关资料

更多关于dojo/request的学习资料请参考:

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 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确实完全正常工作.但是当我点击图像时