翻译 - 【Dojo Tutorials】Ajax with dojo/request
dojo/request是自Dojo1.8才引入的新API,用于客户端向服务端发起请求。本教程主要介绍dojo/request的API:你将会学到如何从服务端获取一个文本文件,如果有错误发生该如何处理,向服务端提交数据,通知API的优点,通过注册使用同样的代码从不同地址请求数据。
开始
dojo/request允许你在不刷新页面的情况下向服务端发送数据或从服务端接收数据(通常所有的AJAX)。这个新功能引入了如何写出更紧凑的代码及如何快速的执行。在dojo/request用于异步编程时,该教程有提到dojo/promise和dojo/Deferred。因为不可能一次把所有东西都学习完,所以在本教程中你只需要了解到promise和Deferred是用于提高非阻塞异步编程的易用性的就好了。在本教程后,你可以去学习关于他们的专题教程。
介绍dojo/request
让我来看一个简单的例子:
1 require(['dojo/request'], function(request) { 2 request('hellowrold.txt').then( 3 function(text) { 4 console.log("The file's content is : " + text); 5 }, 6 function(error) { 7 console.log("An error occurred: " + error); 8 } 9 ); 10 });
在浏览器中,上面的代码使用一个XMLHttpRequest发起一个HTTP GET请求并返回一个dojo/promise/Promise实例。如果请求成功,作为then的第一个参数的函数将被执行,文件中的内容作为该函数的仅有参数被传入;如果请求失败,作为then的第二个参数的函数将被执行,一个错误对象作为该函数的参数被传入。但是如何使用表单向服务端提交数据呢?又或者返回的是JSON或XML呢?没有问题,dojo/request允许你对请求做私人订制。
dojo/request的API
每个请求都需要一样东西:一个目标。正因如此,dojo/request的第一个参数就是请求的URL。
web开发者在他们的工具中需要灵活性,为了能适应它们到他们的应用或者多个环境中。dojo/request正是基于这样的宗旨:第一个必须的参数作为请求的URL。第二个参数是用于定制请求的对象。该对象一些有用的选项如下:
- method - 一个大写的字符串用于表示HTTP请求的方法。为了让指定这些选项更方便提供了一些帮助方法(request.get,request.post,request.put,request.del)。
- sync - 一个布尔值,如果为真表示请求是阻塞的,直到服务端返回数据或者超时。
- query - 一个字符串或者键值对象用于追加到请求URL后面。
- data - 一个字符串,或键值对象,或包含数据的FormData对象用于传递到服务端。
- timeout - 使用毫秒设置请求的超时时间
- handles - 一个字符串表示在将返回的文本数据传递给成功回调函数之前如何处理数据。有几种处理方式,默认的是text,另有json,javascript,xml。
- headers - 一个键值对象用于设置额外的请求头。
让我来看一个使用了一些选项的例子:
1 require(["dojo/request"], function(request) { 2 request.post("post-content.php", { 3 data: { 4 color: "blur", 5 answer: 42 6 }, 7 headers: { 8 "X-Something": "A value" 9 } 10 }).then(function(text) { 11 console.log("The server returned: ", text); 12 }); 13 });
这个例子发送一个HTTP POST请求到post-content.php;一个对象被序列化同一个X-Something请求头被发送到服务端。当服务端返回,内容作为一个值被从request.post返回的promise使用。
例子:request.get和request.post
接下来是一些dojo/request的一般用法。
将文本文件的内容展示到页面上
这个例子使用dojo/request.get请求一个文本文件。这种用法常用于为站点提供条款,条件,隐私的文本,因为如果需要文本文件只需要发送大客户端即可,且在文件中维护文字比在代码中更容易。
1 require(["dojo/dom", "dojo/on", "dojo/request", "dojo/domReady!"], function(dom, on, request) { 2 // Results will be displayed in resultDiv 3 var resultDiv = dom.byId("resultDiv"); 4 5 on(dom.byId("textButton"), "click", function(evt) { 6 request.get("../resources/text/psalm_of_file.txt").then( 7 function(response) { 8 resultDiv.innerHTML = "<pre>" + response + "</pre>"; 9 }, 10 function(error) { 11 resultDiv.innerHTML = "<div class="\error\">" + error "</div>"; 12 } 13 ); 14 }); 15 });
登录演示
下面的这个例子,演示了一个POST请求将用户名和密码发送到服务端并将服务端的返回展示出来:
1 require([ 2 "dojo/dom", 3 "dojo/on", 4 "dojo/request", 5 "dojo/dom-form" 6 ], function(dom, on, request, domForm) { 7 8 var form = dom.byId("formNode"); 9 10 // Attach the onsubmit event handler of the form 11 on(form, "submit", function(evt) { 12 13 // prevent the page from navigating after submit 14 evt.stopPropagation(); 15 evt.preventDefault(); 16 17 // Post the data to the server 18 request.post("../resources/php/login-demo.php", { 19 // Send the username and password 20 data: domForm.toObject("formNode"), 21 // Wait 2 seconds for a response 22 timeout: 2000 23 }).then(function(response) { 24 dom.byId("svrMessage").innerHTML = response; 25 }); 26 }); 27 });
请求头示例
下面这个例子像上面那个一样使用了POST请求,且接收一个请求头Auth-Token。
为了接收请求头,使用了原始Promise的promise.response.getHeader方法(从XHR返回的Promise是没有这个属性的)。另外,当使用promise.response.then的时候,response不是data数据,而是包含了data属性的对象。
1 require([ 2 "dojo/dom", 3 "dojo/on", 4 "dojo/request", 5 "dojo/dom-form" 6 ], function(dom, on, request, domForm) { 7 8 var form = dom.byId("formNode"); 9 10 // Attach the onsubmit event handler of the form 11 on(form, "submit", function(evt) { 12 13 // prevent the page from navigating after submit 14 evt.stopPropagation(); 15 evt.preventDefault(); 16 17 // Post the data to the server 18 var promise = request.post("../resources/php/login-demo.php", { 19 // Send the username and password 20 data: domForm.toObject("formNode"), 21 // Wait 2 seconds for a response 22 timeout: 2000 23 }); 24 25 promise.response.then(function(response) { 26 27 // get the message from the data property 28 var message = response.data; 29 30 // Access the 'Auth-Token' header 31 var token = response.getHeader('Auth-Token'); 32 dom.byId('svrMessage').innerHTML = message; 33 dom.byId('svrToken').innerHTML = token; 34 }); 35 }); 36 });
JSON
JSON是一种常用于AJAX的数据编码方式,因为它易读,易操作,更简洁。JSON可以用于编码任意类型的数据:很多语言都支持JSON格式,包括PHP,Java,Perl,Python,Ruby和ASP。
JSON编码的对象
1 { 2 "title": "JSON Sample Data", 3 "items": [{ 4 "name": "text", 5 "value": "text data" 6 }, { 7 "name": "integer", 8 "value": 100 9 }, { 10 "name": "float", 11 "value": 5.65 12 }, { 13 "name": "boolean", 14 "value": false 15 }] 16 }
当把handleAs设置为"json"时,dojo/request把返回内容作为JSON对待并解析为JavaScript对象。
1 require([ 2 "dojo/dom", 3 "dojo/request", 4 "dojo/json", 5 "dojo/_base/array", 6 "dojo/domReady!" 7 ], function(dom, request, JSON, arrayUtil) { 8 // Results will be displayed in resultDiv 9 var resultDiv = dom.byId("resultDiv"); 10 11 // Request the JSON data form the server 12 request.get("../resources/data/sample.json.php", { 13 // Parse data from JSON to a JavaScript object 14 handleAs: "json" 15 }).then(function(data) { 16 // Display the data sent from the server 17 var html = "<h2>JSON Data</h2>" + 18 "<p>JSON encoded data:</p>" + 19 "<p><code>" + JSON.stringify(data) + "</code></p>" + 20 "<h3>Accessing the JSON data</h3>" + 21 "<p><strong>title</strong> " + data.title + "</p>" + 22 "<p><strong>items</strong> An array of item." + 23 "Each item has a name and a value. The type of " + 24 "the value is shown in parentheses.</p><dl>"; 25 26 arrayUtil.forEach(data.items, function(item, i) { 27 html += "<dt>" + item.name + 28 "</dt><dd>" + item.value + 29 " (" + (typeof item.value) + ")</dd>"; 30 }); 31 html += "</dl>"; 32 33 resultDiv.innerHTML = html; 34 }, function(error) { 35 // Display the error returned 36 resultDiv.innerHTML = error; 37 }); 38 });
JSONP
AJAX请求被限制在了同域中。如果你需要从不同的域获取数据,你可以使用JSONP。当使用JSONP时,一个script标签被嵌入到当前页面,它的src文件被请求,服务端将数据包裹在一个回调函数中,当响应被解析,回调函数将被调用,数据作为它的第一个参数传入。可以使用dojo/request/script发起JSONP请求。
让我们看一些例子:
使用JSONP从服务端请求数据并处理响应内容
1 require([ 2 "dojo/dom", 3 "dojo/on", 4 "dojo/request/script", 5 "dojo/json", 6 "dojo/domReady!" 7 ], function(dom, on, script, JSON) { 8 // Results will be displayed in resultDiv 9 var resultDiv = dom.byId("resultDiv"); 10 11 // Attach the onclick event handler to the makeRequest button 12 on(dom.byId("makeRequest"), "click", function(evt) { 13 14 // When the makeRequest button is clicked, send the current 15 // date an time to the server in a JSONP request 16 var d = new Date(), 17 dateNow = d.toString(); 18 script.get("../resources/php/jsonp-demo.php", { 19 // Tell the server that the callback name to 20 // use is in the "callback" query parameter 21 jsonp: "callback", 22 // Send the date and time 23 query: { 24 clienttime: dateNow 25 } 26 }).then(function(data) { 27 // Display the result 28 resultDiv.innerHTML = JSON.stringify(data); 29 }); 30 }); 31 });
使用JSONP从GitHub API请求Dojo的pull请求
1 require([ 2 "dojo/dom", 3 "dojo/on", 4 "dojo/request/script", 5 "dojo/dom-construct", 6 "dojo/_base/array", 7 "dojo/domReady!" 8 ], function(dom, on, script, domContruct, arrayUtil){ 9 var pullsNode = dom.byId("pullrequests"); 10 11 // Attach the onclick event handler to tweetButton 12 on(dom.byId("pullrequestsButton"), "click", function(evt) { 13 // Request the open pull requests from Dojo's GitHub repo 14 script.get("https://api.github.com/repos/dojo/dojo/pulls", { 15 // Use the "callback" query parameter to tell 16 // GitHub's services the name of the function 17 // to wrap the data in 18 jsonp: "callback" 19 }).then(function(response) { 20 // Empty the tweets node 21 domContruct.empty(pullsNode); 22 23 // Create a document fragment to keep from 24 // doing live DOM manipulation 25 var fragment = document.createDocumentFragment(); 26 27 // Loop through each pull request and create a list item 28 // for it 29 arrayUtil.forEach(response.data, function(pull) { 30 var li = domContruct.create("li", {}, fragment); 31 var link = domContruct.create("a", {href: pull.url, innerHTML: pull.title}, li); 32 }); 33 34 // Append the document fragment to the list 35 domContruct.place(fragment, pullsNode); 36 }); 37 }); 38 });
报告状态
dojo/request/notify提供了一种机制,用于向使用dojo/request发起的请求报告状态。dojo/request/notify允许供应者出发事件,可以被监听,用于报告请求的状态。为了监听一个事件,调用dojo/request/notify模块返回的值,且带有两个参数:一个事件名称和监听器函数。下面是dojo/request提供者可以出发的事件:
dojo/request/notify支持的事件
- start
- send
- load
- error
- done
- stop
start与stop的监听器没有参数。send的监听器接受两个参数:一个表示请求的对象和一个取消函数。调用取消函数将会在请求开始之前取消它。load,error和done的监听器接受一个参数:一个表示从服务端响应的数据。让我们看一些实战例子:
使用dojo/request/notify监视请求进度
1 require([ 2 "dojo/dom", 3 "dojo/request", 4 "dojo/request/notify", 5 "dojo/on", 6 "dojo/dom-construct", 7 "dojo/query", 8 "dojo/domReady!" 9 ], function(dom, request, notify, on, domContruct) { 10 // Listen for events from request providers 11 notify("start", function() { 12 domContruct.place("<p>Sent request</p>", "divStatus"); 13 }); 14 15 notify("send", function(data, cancel) { 16 domContruct.place("<p>Sent request</p>", "divStatus"); 17 }); 18 19 notify("load", function(data) { 20 domContruct.place("<p>Load (response received)</p>", "divStatus"); 21 }); 22 23 notify("error", function(error) { 24 domContruct.place("<p class=\"error\">Error</p>", "divStatus"); 25 }); 26 27 notify("done", function(data) { 28 domContruct.place("<p>Done (response processed)</p>", "divStatus"); 29 if(data instanceof Error) { 30 domContruct.place("<p class=\"error\">Error</p>", "divStatus"); 31 } else { 32 domContruct.place("<p class=\"success\">Success</p>", "divStatus"); 33 } 34 }); 35 notify("stop", function() { 36 domContruct.place("<p>Stop</p>", "divStatus"); 37 domContruct.place("<p class=\"ready\">Ready</p>", "divStatus"); 38 }); 39 40 // Use event delegation to only listen for clicks that 41 // come from nodes with a class of "action" 42 on(dom.byId("buttonContainer"), ".action:click", function(evt) { 43 domContruct.empty("divStatus"); 44 request.get("../resources/php/notify-demo.php", { 45 query: { 46 success: this.id === "successBtn" 47 }, 48 handleAs: "json" 49 }); 50 }); 51 });
dojo/request/registry
dojo/request/registry提供了一种机制,基于请求URL来路由请求。一般用法是基于请求是否在同域指派一个提供者,当前域使用JSON,否则使用JSONP。如果在进程中你的URL基于操作有所变化,你也会使用这种方法的。
dojo/request/registry语法
1 request.register(url, provider, first);
dojo/request/registry参数
- url - url可以是字符串,正则表达式或者函数
- string - 如果是字符串,提供者将使用它如果url匹配上
- regExp -
- function -
- provider - 处理的请求的提供者
- first - 是个可选的布尔参数,如果为真,注册器将提供者加到已有的提供者前面。
让我来看看最终的例子:
使用dojo/request/registry根据请求的URL指派提供者
1 require([ 2 "dojo/request/registry", 3 "dojo/request/script", 4 "dojo/dom", 5 "dojo/dom-construct", 6 "dojo/on", 7 "dojo/domReady!" 8 ], function(request, script, dom, domContruct, on) { 9 // Registers anything that starts with "http://" 10 // to be sent to the script provider 11 // requests for a local search will use xhr 12 request.register(/^https?:\/\//i, script); 13 14 // When the search button is clicked 15 on(dom.byId("searchButton"), "click", function() { 16 // First send a request to twitter for all tweets 17 // tagged with the search string 18 request("http://search.twitter.com/search.json", { 19 query: { 20 q: "#" + dom.byId("searchText").value, 21 result_type: "mixed", 22 lang: "en" 23 }, 24 jsonp: "callback" 25 }).then(function(data) { 26 // If the tweets node exists, destroy it 27 if(dom.byId("tweets")) { 28 domContruct.destroy("tweets"); 29 } 30 // If at least one result was returned 31 if (data.results.length > 0) { 32 // Create a new tweet list 33 domContruct.create("ul", {id: "tweets"}, "twitterDiv"); 34 // Add each tweet as a li 35 while(data.results.length > ) { 36 domContruct.create("li", {innerHTML: data.result.shift().text}, "tweets"); 37 } 38 } else { 39 // No results returned 40 domContruct.create("p", {id: "tweets", innerHTML: "None"}, "twitterDiv"); 41 } 42 }); 43 44 // Next send a request to the local search 45 request("../resources/php/search.php", { 46 query: { 47 q: dom.byId("searchText").value 48 }, 49 handleAs: "json" 50 }).then(function(data) { 51 dom.byId("localResourceDiv").innerHTML = 52 "<p><strong>" + data.name + "</strong><br/>" + 53 "<a href=\"" + data.url + "\">" + data.url + "</a><br/>"; 54 }, function(error) { 55 // If no results are found, the local search results a 404 56 dom.byId("localResourceDiv").innerHTML = "<p>None</p>"; 57 }); 58 }); 59 });
最佳实践
dojo/request的最佳实践包括:
- 谨慎的选择请求方法。一般情况下,GET用于没有安全考虑的请求。GET通常比POST快。POST通常用于发送表单数据且数据不会附加在URL中。
- 应该在HTTPS的页面上发起HTTPS的请求。
- 由于AJAX请求不会刷新页面,用户都比较喜欢状态更新,从加载到结束。
- 使用有效的开发工具解决问题更快速。
- 在尽可能多的浏览器上测试你的应用。
总结
dojo/request为请求当前域及其他域提供了一个跨浏览器遵循AJAX接口,包括优雅的错误处理,支持通知,根据URL路由请求。dojo/request返回一个promise,允许一系列的请求被发布和异步处理响应内容。页面可以包含多个源的内容,使用每个请求返回的数据。使用dojo/request增强你的页面吧!