原文:http://dojotoolkit.org/documentation/tutorials/1.10/ajax/難度等級:初級 Dojo 版本:1.10
起步
dojo/request 允許你無需重新整理頁面就可以向 server 發送或接收資料(也就是大家熟知的 AJAX)。本章介紹的新特性可以使編寫的代碼更緊湊,執行的更快速。本章内容涉及 dojo/promise 和 dojo/Deferred,這些子產品被 dojo/request 用來進行異步程式設計。本章不能涵蓋所有内容,但請記住 promises 和 Deferreds可以讓編寫非阻塞異步代碼更為簡單。本章之後,你再學習更多教程。
dojo/request 介紹
先看個簡單例子:
require(["dojo/request"], function(request){
request("helloworld.txt").then(
function(text){
console.log("The file's content is: " + text);
},
function(error){
console.log("An error occurred: " + error);
}
);
});
在浏覽器中,以上代碼會使用 XMLHttpRequest 發出一個HTTP GET 請求,擷取 helloworld.txt ,并且傳回一個。如果請求成功,then() 當中的第一個 function 會執行,檔案的文本内容是其唯一參數;如果請求失敗,then() 當中的第二個 function 會執行,參數是一個 error 對象。如果我們需要發送表單資料到 server 該怎麼做?響應的資料為 JSON 或 XML 又該如何處理?都沒問題 -- dojo/request API 允許定制 request。The dojo/request API
每次 request 都需要一樣東西:對端 (end-point)。是以,dojo/request 的第一個參數就是請求的URL 。
web 開發者希望他們的工具足夠靈活,可以按應用要求或不同的環境進行調整。dojo/request API 可以很好的達到這個要求:dojo/request 的第一個必選的參數是請求的 URL。第二個參數使用一個 object 來定制請求。其中最有用的選項是:
method - 大寫的字元串,指明發出 request 時 HTTP 的方法類型。有幾個輔助函數可以簡化操作(request.get,request.post, request.put, request.del).
sync - boolean 值,如果為 true,則阻塞 request ,直到 server 響應或等待逾時。
query - 字元串,或一個包含鍵值對的 object,用于追加查詢參數到 URL 。
data - 字元串,或一個包含鍵值對的 object,或 FormData object,用于包含需要傳給 server 的資料 。
timeout - 設定逾時值(毫秒),并設定異常處理句柄。
handleAs - 字元串,用來指定如何轉換響應傳回的有效資料,轉換後送入回調函數( success handler)。可用的格式有: "text" (預設), "json", "javascript", 和 "xml"。
headers - 用來設定随 request 一起發送的額外的頭部資訊。
我們來看一個具體例子:
require(["dojo/request"], function(request){
request.post("post-content.php", {
data: {
color: "blue",
answer: 42
},
headers: {
"X-Something": "A value"
}
}).then(function(text){
console.log("The server returned: ", text);
});
});
這個例子發送一個 HTTP POST 到 post-content.php;将一個簡單的對象 (data那部分) 串行化并作為 POST 資料随 request 一起發送,同時附帶"X-Something" 頭部資訊。server 響應後,傳回的有效資料将傳入request.post 生成的 promise 中。例子: request.get 和request.post
下面是 dojo/request 的一些常見用法。
在頁面中顯示一個文本檔案的内容
這個例子使用 dojo/request.get 請求一個 text 檔案。這種設計一般用于提供某種靜态資訊如伺服器環境、配置資訊等,因為伺服器隻是響應請求,将檔案發送到本地(譯注:言下之意,你沒法修改它),很ming在服務端維護個靜态文本比維護代碼要簡單。
譯注:由于嵌入代碼包含 html 塊級标簽 ,顯示不大正常,統一在标簽前面加“!”,原始代碼請參考原文。
require(["dojo/dom", "dojo/on", "dojo/request", "dojo/domReady!"],
function(dom, on, request){
// Results will be displayed in resultDiv
var resultDiv = dom.byId("resultDiv");
// 給 textButton 綁定onclick 事件句柄
on(dom.byId("textButton"), "click", function(evt){
// Request the text file
request.get("../resources/text/psalm_of_life.txt").then(
function(response){
// 顯示文本檔案内容
resultDiv.innerHTML = ""+response+"";
},
function(error){
// 顯示錯誤資訊
resultDiv.innerHTML = "error\">"+error+"";
}
);
});
}
);
View Demo
登入示範
下面這個例子,用 POST 請求将使用者名和密碼發送到 server,然後顯示 server 傳回結果。
require(["dojo/dom", "dojo/on", "dojo/request", "dojo/dom-form"],
function(dom, on, request, domForm){
var form = dom.byId('formNode');
// 關聯表單的 onsubmit 事件句柄
on(form, "submit", function(evt){
// prevent the page from navigating after submit
evt.stopPropagation();
evt.preventDefault();
// Post 資料到 server
request.post("../resources/php/login-demo.php", {
// 發送使用者名和密碼
data: domForm.toObject("formNode"),
// 設定2秒響應逾時
timeout: 2000
}).then(function(response){
dom.byId('svrMessage').innerHTML = response;
});
});
}
);
View Demo
Headers 示範
下面這個例子,同上個例子一樣使用 POST 請求,增加了一個 Auth-Token 頭部的通路。(注:Auth-Token 似乎同安全有關,可以防止 cookie 被複制盜用)
為通路頭部資訊,我們使用原生 Promise 的 promise.response.getHeader 方法 (從 XHR 傳回的 Promise 沒有這個屬性)。另外,使用 promise.response.then,response 就不再是簡單的 data,而是一個包含 data 屬性的對象。
require(["dojo/dom", "dojo/on", "dojo/request", "dojo/dom-form"],
function(dom, on, request, domForm){
// 結果會顯示在 resultDiv 中
var form = dom.byId('formNode');
// 關聯表單的onsubmit 事件句柄
on(form, "submit", function(evt){
// prevent the page from navigating after submit
evt.stopPropagation();
evt.preventDefault();
// Post 資料到server
var promise = request.post("../resources/php/login-demo.php", {
// 發送使用者名和密碼
data: domForm.toObject("formNode"),
// 設定2秒響應逾時
timeout: 2000
});
// 不再使用 promise.then ,而是使用 promise.response.then
promise.response.then(function(response){
// 從 data 屬性擷取資訊
var message = response.data;
// 通路 'Auth-Token' 頭部資訊
var token = response.getHeader('Auth-Token');
dom.byId('svrMessage').innerHTML = message;
dom.byId('svrToken').innerHTML = token;
});
});
}
);
View Demo
JSON (JavaScript Object Notation)
在 AJAX 請求中 是一種非常通用的資料編碼方式,因為它易于閱讀,便于操作,格式緊湊。JSON 可以編碼(encode)任何類型的資料:很多語言都包含或支援 JSON 格式,如 , , , , , 和 。
JSON encoded object
{
"title":"JSON Sample Data",
"items":[{
"name":"text",
"value":"text data"
},{
"name":"integer",
"value":100
},{
"name":"float",
"value":5.65
},{
"name":"boolean",
"value":false
}]
}
如果把 handleAs 設定為 "json", dojo/request 會将 response 的有效資料作為 JSON 資料進行解碼,轉換成 JavaScript 對象。
譯注:由于嵌入代碼包含 html 塊級标簽 ,顯示不大正常,統一在标簽前面加“!”,原始代碼請參考原文。
require(["dojo/dom", "dojo/request", "dojo/json",
"dojo/_base/array", "dojo/domReady!"],
function(dom, request, JSON, arrayUtil){
// Results will be displayed in resultDiv
var resultDiv = dom.byId("resultDiv");
// Request the JSON data from the server
request.get("../resources/data/sample.json.php", {
// Parse data from JSON to a JavaScript object
handleAs: "json"
}).then(function(data){
// Display the data sent from the server
var html = "
JSON Data" +
"JSON encoded data:" +
"" + JSON.stringify(data) + ""+
"
Accessing the JSON data" +
"title " + data.title + "" +
"items An array of items." +
"Each item has a name and a value. The type of " +
"the value is shown in parentheses.";
arrayUtil.forEach(data.items, function(item,i){
html += "" + item.name +
"" + item.value +
" (" + (typeof item.value) + ")";
});
html += "";
resultDiv.innerHTML = html;
},
function(error){
// Display the error returned
resultDiv.innerHTML = error;
});
}
);
In addition to the encoding the data as JSON in the response, set the header to application/json, either using server configuration such as or adding it to the header with the server side code.
View Demo
JSONP (Javascript Object Notation with Padding)
AJAX 請求不能跨域。需要跨域的話,可以使用 。使用 JSONP時,一個 script 标簽會被插入到目前頁面中, the src file is requested, 服務端會将資料包裹在一個回調函數中,當 response 被解析時, the callback is called with the data as its first argument. 可以使用 發出 JSONP 請求。
看些例子吧:
使用 JSONP 從服務端請求資料,并處理響應
require(["dojo/dom", "dojo/on", "dojo/request/script",
"dojo/json", "dojo/domReady!"
], function(dom, on, script, JSON){
// 結果會顯示在 resultDiv 中
var resultDiv = dom.byId("resultDiv");
// 給 makeRequest 綁定 onclick 事件句柄
on(dom.byId('makeRequest'),"click", function(evt){
// 按鈕點選後,以 JSONP 請求形式發送本地日期、時間到 server
var d = new Date(),
dateNow = d.toString();
script.get("../resources/php/jsonp-demo.php",{
// Tell the server that the callback name to
// use is in the "callback" query parameter
jsonp: "callback",
// 發送日期、時間
query: {
clienttime: dateNow
}
}).then(function(data){
// 顯示結果
resultDiv.innerHTML = JSON.stringify(data);
});
});
});
因為 response 是 JavaScript 代碼,不是 JSON 資料,是以response 中的 Content-Type header 應該是 application/javascript。
View Demo
Using JSONP to request Dojo pull requests from the GitHub API
require(["dojo/dom", "dojo/on", "dojo/request/script",
"dojo/dom-construct", "dojo/_base/array",
"dojo/domReady!"
], function(dom, on, script, domConstruct, arrayUtil){
var pullsNode = dom.byId("pullrequests");
// 給 tweetButton 關聯 onclick 事件句柄
on(dom.byId("pullrequestsButton"), "click", function(evt){
// 向 Dojo's GitHub repo的開放 pull 接口發起請求
script.get("", {
// 通過 "callback" query 參數告訴 GitHub伺服器,用來包裹資料的函數名稱是什麼
jsonp: "callback"
}).then(function(response){
// 清空 tweets 節點
domConstruct.empty(pullsNode);
// Create a document fragment to keep from
//doing live DOM manipulation
var fragment = document.createDocumentFragment();
// 周遊每一個 pull 請求,為每一項建立一個清單項
arrayUtil.forEach(response.data, function(pull){
var li = domConstruct.create("li", {}, fragment);
var link = domConstruct.create("a", {href: pull.url, innerHTML: pull.title}, li);
});
// 将文檔片段加入到清單中
domConstruct.place(fragment, pullsNode);
});
});
});
View Demo
Reporting Status
提供了一種機制,用于報告 dojo/request 送出的請求的狀态 (也可是 dojo/request 中任何其他 provider)。加載 dojo/request/notify 子產品就可以允許 providers 發射事件,我們就可以監聽事件,據此彙報請求狀态。要監聽一個事件,需要給加載 dojo/request/notify 子產品後的句柄傳遞兩個參數:事件名稱和監聽函數( listener function)。下面是 dojo/request providers 可發射的事件:
支援的 dojo/request/notify 事件
start - 當請求第一次發送出去時發射
send - Emitted prior to a provider sending a request
load - 當 provider 收到響應成功的 response 時發射
error - 當 provider 收到錯誤時發射
done - 當 provider 完成 request 時發射,無論請求成功與否
stop - 當所有的在途 requests 都結束時發射
"start" 和 "stop" 上的 listener 無需參數。"send" 上的 listener 接收兩個參數: 一個代表 request 的對象和一個登出函數。調用登出函數就會在 request開始前将其取消。"load", "error", 和 "done" 的 listener 接收一個參數:一個代表服務端 response 的對象。我們來看一個執行個體:
使用 dojo/request/notify 監控 requests 進度
譯注:由于嵌入代碼包含 html 塊級标簽 ,顯示不大正常,統一在标簽前面加“!”,原始代碼請參考原文。
require(["dojo/dom", "dojo/request", "dojo/request/notify",
"dojo/on", "dojo/dom-construct", "dojo/query",
"dojo/domReady!"],
function(dom, request, notify, on, domConstruct){
// Listen for events from request providers
notify("start", function(){
domConstruct.place("Start","divStatus");
});
notify("send", function(data, cancel){
domConstruct.place("Sent request","divStatus");
});
notify("load", function(data){
domConstruct.place("Load (response received)","divStatus");
});
notify("error", function(error){
domConstruct.place("Error","divStatus");
});
notify("done", function(data){
domConstruct.place("Done (response processed)","divStatus");
if(data instanceof Error){
domConstruct.place("Error","divStatus");
}else{
domConstruct.place("Success","divStatus");
}
});
notify("stop", function(){
domConstruct.place("Stop","divStatus");
domConstruct.place("Ready", "divStatus");
});
// Use event delegation to only listen for clicks that
// come from nodes with a class of "action"
on(dom.byId("buttonContainer"), ".action:click", function(evt){
domConstruct.empty("divStatus");
request.get("../resources/php/notify-demo.php", {
query: {
success: this.id === "successBtn"
},
handleAs: "json"
});
});
}
);
dojo/request/registry
provides a mechanism to route requests based on the URL requested. Common uses of the registry are to assign a provider based on whether the request will be made to the current domain using JSON, or to a different domain using JSONP. You may also use this approach if the URLs can vary based on the operations in progress.
dojo/request/registry 文法
request.register(url, provider, first);
dojo/request/registry 參數
url - url 是一個字元串,正規表達式(regEx),或者函數。
string - 如果 url 是字元串,則在 url 完全比對後provider 将啟用。
regExp - 如果 url 是正規表達式,則在請求的URL 比對後 provider 将啟用。
function - 如果 url 是個函數,URL 以及 request 附帶的選項 object 會傳給函數。當函數執行傳回 true 時,provider 将啟用。
provider - 指定用來處理 request 請求的 provider
first - 一個可選參數,為 boolean 值。如果為真,在已注冊的 providers 前注冊這個 provider(譯注:有優先級嗎?)。
看一個最終的例子:
使用 dojo/request/registry ,基于請求的 URL ,關聯 provider
譯注:由于嵌入代碼包含 html 塊級标簽 ,顯示不大正常,統一在标簽前面加“!”,原始代碼請參考原文。require(["dojo/request/registry", "dojo/request/script", "dojo/dom",
"dojo/dom-construct", "dojo/on", "dojo/domReady!"],
function(request, script, dom, domConstuct, on){
// 将所有通路 "http://" 的請求注冊到 script provider
// 而對本地資源的通路将使用 xhr
request.register(/^https?:\/\//i, script);
// When the search button is clicked
on(dom.byId("searchButton"), "click", function(){
// First send a request to twitter for all tweets
// tagged with the search string
request("", {
query: {
q:"#" + dom.byId("searchText").value,
result_type:"mixed",
lang:"en"
},
jsonp: "callback"
}).then(function(data){
// 如果 tweets 節點已存在了,銷毀它
if (dom.byId("tweets")){
domConstuct.destroy("tweets");
}
// 如果至少有一個傳回結果
if (data.results.length > 0) {
// 建立一個新的 tweet 清單
domConstuct.create("ul", {id: "tweets"},"twitterDiv");
// 将每一個 tweet 添加成一個 li
while (data.results.length>0){
domConstuct.create("li", {innerHTML: data.results.shift().text},"tweets");
}
}else{
// 如果沒有結果傳回
domConstuct.create("p", {id:"tweets",innerHTML:"None"},"twitterDiv");
}
});
// 接下來發送一個本地檢索的 request
request("../resources/php/search.php", {
query: {
q: dom.byId("searchText").value
},
handleAs: "json"
}).then(
function(data){
dom.byId('localResourceDiv').innerHTML =
"" + data.name + "" +
},
function(error){
// 如果沒有搜尋結果,本地搜尋會傳回一個 404
dom.byId('localResourceDiv').innerHTML = "None";
}
);
});
}
);
最佳實踐
使用 dojo/request 的最佳實踐:
仔細選擇 request 方式method。GET通常用于簡單的非安全性資料擷取。Get一般比 POST 快。POST通常用于發送難以通過 URL 傳遞的表單資料 。
對需要保護的資料或者 HTTPS 頁面使用 HTTPS 。
AJAX 請求不需重新整理頁面,是以大多數人都會做一個狀态提示,顯示 加載... 直到完成。
為更好地檢測及恢複 request 錯誤,要使用錯誤回調。
使用有效的開發工具可以更快的解決問題。
盡可能多用幾種浏覽器仔細測試你的代碼。
小結
dojo/request 提供了一個跨浏覽器相容的 AJAX 接口,可以實作本地以及跨域請求,有設計良好的錯誤處理,支援資訊通告,支援基于 URL 的 request 路由分發。dojo/request 調用的傳回值是個 promise,運作批量發出 requests 然後異步處理 responses。頁面内容可以包括多個資料源,每個資料源請求完成後資料立即可用。快使用 dojo/request 加速你的頁面吧!
其他資源