深入浅出dojo/request

随着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环境上都是一致的:
[javascript] view plain copy
  1. <spanstyle="font-size:14px;">require(["dojo/request"],function(request){
  2. varpromise=request(url,options);
  3. promise.then(
  4. function(data){
  5. },
  6. function(error){
  7. }
  8. );
  9. promise.response.then(
  10. function(response){
  11. },
  12. function(error){
  13. }
  14. );
  15. request.get(url,options).then(...);
  16. request.post(url,options).then(...);
  17. request.put(url,options).then(...);
  18. request.del(url,options).then(...);
  19. });</span>
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属性
  1. <spanstyle="font-size:14px;"><script>
  2. vardojoConfig={
  3. requestProvider:"dojo/request/script"
  4. };
  5. </script>
  6. <scriptsrc="path/to/dojo/dojo.js"></script></span>
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函数
[javascript] view plain copy
  1. <spanstyle="font-size:14px;">//如果一个请求的URL是"some/url",provider就会被用来处理这个请求
  2. registry.register("some/url",provider);
  3. //如果一个请求的URL以"some/url"开始,provider就会被用来处理这个请求
  4. registry.register(/^some\/url/,provider);
  5. //如果一个请求是一HTTPGET方法发送的,provider就会被用来处理这个请求
  6. registry.register(
  7. function(url,options){
  8. returnoptions.method==="GET";
  9. },
  10. provider
  11. );
  12. //如果不能匹配到任何已注册的条件,认provider将被使用
  13. </span>
如果所有条件都不满足,而且也没有备用provider可用,那么当前环境的认provider将被启用。由于dojo/request/registry符合dojo/request API,它可以作为认provider:
  1. <spanstyle="font-size:14px;"><script>
  2. vardojoConfig={
  3. requestProvider:"dojo/request/registry"
  4. };
  5. </script>
  6. <scriptsrc="path/to/dojo/dojo.js"></script>
  7. <script>
  8. require(["dojo/request","dojo/request/script"],
  9. function(request,script){
  10. request.register(/^\/jsonp\//,script);
  11. ...
  12. }
  13. );
  14. </script></span>
如果我们想要使用平台认的provider(对于浏览器来说是XHR)作为备用,那这样做当然很好,但我们也可以通过上例中的最后一条语句设置自己的备用provider,只是在这句话之后就不能再注册其他provider了。设置在requestProvider参数上的dojo/request/registry还可以接受插件作为备用provider:
  1. <spanstyle="font-size:14px;"><script>
  2. vardojoConfig={
  3. requestProvider:"dojo/request/registry!my/authProvider"
  4. };
  5. </script>
  6. <scriptsrc="path/to/dojo/dojo.js"></script>
  7. <script>
  8. require(["dojo/request",script);
  9. ...
  10. }
  11. );
  12. </script></span>
好了,现在任何不满足预设条件的请求都会由my/authProvider来处理。

注册provide功能的强大可能还不是那么明显。现在让我们来看几个让注册功能大显身手的场景。首先,考虑一个应用,它的服务器端API是在不断变化的。也就是说虽然我们知道每一个具体的终端服务,但我们不知道会需要什么样的头部信息,甚至也不知道响应中会返回什么样的JSON对象。我们可以很容易地为每一项服务注册一个临时provider,然后立马开始开发用户界面。假设我们猜想/service1将在items属性中返回JSON格式的数据项,而/service2将在data属性中返回这些数据项:
[javascript] view plain copy
  1. <spanstyle="font-size:14px;">request.register(/^\/service1\//,function(url,options){
  2. varpromise=xhr(url,lang.delegate(options,{handleAs:"json"})),
  3. dataPromise=promise.then(function(data){
  4. returndata.items;
  5. });
  6. returnlang.delegate(dataPromise,{
  7. response:promise.response
  8. });
  9. });
  10. request.register(/^\/service2\//,options){
  11. varpromise=xhr(url,
  12. dataPromise=promise.then(function(data){
  13. returndata.data;
  14. });
  15. returnlang.delegate(dataPromise,{
  16. response:promise.response
  17. });
  18. });</span>
现在所有在用户界面中用到的服务请求都可以通过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的学习资料请参考:

相关文章

我有一个网格,可以根据更大的树结构编辑小块数据.为了更容易...
我即将开始开发一款教育性的视频游戏.我已经决定以一种我可以...
我正在使用带有Grails2.3.9的Dojo1.9.DojoNumberTextBox小部...
1.引言鉴于个人需求的转变,本系列将记录自学arcgisapiforja...
我正在阅读使用dojo’sdeclare进行类创建的语法.描述令人困惑...
我的团队由更多的java人员和JavaScript经验丰富组成.我知道这...