难度:中等
Dojo版本:1.7
原作者:Bryan Forbes
译者:Oliver (zhuxw1984@gmail.com)
原文链接:http://www.sitepen.com/blog/2012/08/21/introducing-dojorequest/
随着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", "dojo/request/script"],
function(request, script){
request.register(/^\/jsonp\//, 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\//, function(url, options){
var promise = xhr(url, lang.delegate(options, { handleAs: "json" })),
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的学习资料请参考:
- Ajax with dojo/request tutorial
- dojo/request reference guide
- dojo/request API