随着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环境上都是一致的:
- <spanstyle="font-size:14px;">require(["dojo/request"],function(request){
- varpromise=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(...);
- });</span>
- 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部分的文本。
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属性:
- <spanstyle="font-size:14px;"><script>
- vardojoConfig={
- requestProvider:"dojo/request/script"
- };
- </script>
- <scriptsrc="path/to/dojo/dojo.js"></script></span>
虽然比起直接使用非默认provider来,将其配置成默认能为我们提供更大的灵活性,但这么做仍然不能只通过一个API(dojo/request)就做到根据预设条件来自动使用不同provider的能力。假设我们的应用有一些数据服务,其中一个服务需要一组用于身份验证的头部信息,而另一个需要完全不同的另一组头部信息。或者一个需要JSON-P而另一个需要XMLHttpRequest。这种情况下就是dojo/request/registry闪亮登场的时候了。
注册机制
Dojox中有一个存在了很久但并没有得到广泛使用的模块dojox/io/xhrPlugins。这个模块可以让dojo.xhr*成为所有请求的接口,无论这些请求是通过JSONP发送还是iframe,甚至是其他用户自定义的方式。这个统一接口的思想非常有用,因此被沿用到dojo/request/registry中。
- <spanstyle="font-size:14px;">//如果一个请求的URL是"some/url",provider就会被用来处理这个请求
- registry.register("some/url",provider);
- //如果一个请求的URL以"some/url"开始,provider就会被用来处理这个请求
- registry.register(/^some\/url/,provider);
- //如果一个请求是一HTTPGET方法发送的,provider就会被用来处理这个请求
- registry.register(
- function(url,options){
- returnoptions.method==="GET";
- },
- provider
- );
- //如果不能匹配到任何已注册的条件,默认provider将被使用
- </span>
如果所有条件都不满足,而且也没有备用provider可用,那么当前环境的默认provider将被启用。由于dojo/request/registry符合dojo/request API,它可以作为默认provider:
- <spanstyle="font-size:14px;"><script>
- vardojoConfig={
- requestProvider:"dojo/request/registry"
- };
- </script>
- <scriptsrc="path/to/dojo/dojo.js"></script>
- <script>
- require(["dojo/request","dojo/request/script"],
- function(request,script){
- request.register(/^\/jsonp\//,script);
- ...
- }
- );
- </script></span>
如果我们想要使用平台默认的provider(对于浏览器来说是XHR)作为备用,那这样做当然很好,但我们也可以通过上例中的最后一条语句设置自己的备用provider,只是在这句话之后就不能再注册其他provider了。设置在requestProvider参数上的dojo/request/registry还可以接受插件作为备用provider:
- <spanstyle="font-size:14px;"><script>
- vardojoConfig={
- requestProvider:"dojo/request/registry!my/authProvider"
- };
- </script>
- <scriptsrc="path/to/dojo/dojo.js"></script>
- <script>
- require(["dojo/request",script);
- ...
- }
- );
- </script></span>
好了,现在任何不满足预设条件的请求都会由my/authProvider来处理。
注册provide功能的强大可能还不是那么明显。现在让我们来看几个让注册功能大显身手的场景。首先,考虑一个应用,它的服务器端API是在不断变化的。也就是说虽然我们知道每一个具体的终端服务,但我们不知道会需要什么样的头部信息,甚至也不知道响应中会返回什么样的JSON对象。我们可以很容易地为每一项服务注册一个临时provider,然后立马开始开发用户界面。假设我们猜想/service1将在items属性中返回JSON格式的数据项,而/service2将在data属性中返回这些数据项:
- <spanstyle="font-size:14px;">request.register(/^\/service1\//,function(url,options){
- varpromise=xhr(url,lang.delegate(options,{handleAs:"json"})),
- dataPromise=promise.then(function(data){
- returndata.items;
- });
- returnlang.delegate(dataPromise,{
- response:promise.response
- });
- });
- request.register(/^\/service2\//,options){
- varpromise=xhr(url,
- dataPromise=promise.then(function(data){
- returndata.data;
- });
- returnlang.delegate(dataPromise,{
- response:promise.response
- });
- });</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的学习资料请参考:
原文链接:https://www.f2er.com/dojo/291345.html