XMLHttpRequest 是內建於瀏覽器的物件,允許在 JavaScript 中發出 HTTP 請求。
儘管名稱中有「XML」,但它可以處理任何資料,不只 XML 格式。我們可以上傳/下載檔案、追蹤進度等等。
目前有另一個較新的方法 fetch,在某種程度上取代了 XMLHttpRequest。
在現代網頁開發中,XMLHttpRequest 因以下三個原因而被使用
- 歷史原因:我們需要支援現有的
XMLHttpRequest腳本。 - 我們需要支援舊瀏覽器,而且不想要多重填充(例如,保持腳本精簡)。
- 我們需要
fetch目前無法執行的功能,例如追蹤上傳進度。
聽起來很熟悉嗎?如果是,那麼沒問題,請繼續使用 XMLHttpRequest。否則,請前往 Fetch。
基礎知識
XMLHttpRequest 有兩種操作模式:同步和非同步。
讓我們先看看非同步,因為它在大部分情況下都被使用。
要執行請求,我們需要 3 個步驟
-
建立
XMLHttpRequestlet xhr = new XMLHttpRequest();建構函式沒有參數。
-
初始化,通常在
new XMLHttpRequest之後xhr.open(method, URL, [async, user, password])此方法指定請求的主要參數
method– HTTP 方法。通常為"GET"或"POST"。URL– 要請求的 URL,字串,可以是 URL 物件。async– 如果明確設定為false,則請求為同步,我們稍後會說明。user、password– 基本 HTTP 驗證的登入和密碼(如果需要)。
請注意,
open呼叫與其名稱相反,並未開啟連線。它只會設定請求,但網路活動僅從send的呼叫開始。 -
發送出去。
xhr.send([body])此方法開啟連線並將請求傳送至伺服器。選用的
body參數包含請求主體。某些請求方法(例如
GET)沒有主體。而某些方法(例如POST)使用body將資料傳送至伺服器。我們稍後會看到範例。 -
聆聽
xhr事件以取得回應。這三個事件是最廣泛使用的
load– 當請求完成(即使 HTTP 狀態為 400 或 500)且回應已完全下載。error– 當無法進行請求,例如網路中斷或 URL 無效。progress– 在回應下載期間定期觸發,報告已下載的數量。
xhr.onload = function() { alert(`Loaded: ${xhr.status} ${xhr.response}`); }; xhr.onerror = function() { // only triggers if the request couldn't be made at all alert(`Network Error`); }; xhr.onprogress = function(event) { // triggers periodically // event.loaded - how many bytes downloaded // event.lengthComputable = true if the server sent Content-Length header // event.total - total number of bytes (if lengthComputable) alert(`Received ${event.loaded} of ${event.total}`); };
以下是一個完整的範例。以下程式碼從伺服器載入 /article/xmlhttprequest/example/load 的 URL 並列印進度
// 1. Create a new XMLHttpRequest object
let xhr = new XMLHttpRequest();
// 2. Configure it: GET-request for the URL /article/.../load
xhr.open('GET', '/article/xmlhttprequest/example/load');
// 3. Send the request over the network
xhr.send();
// 4. This will be called after the response is received
xhr.onload = function() {
if (xhr.status != 200) { // analyze HTTP status of the response
alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found
} else { // show the result
alert(`Done, got ${xhr.response.length} bytes`); // response is the server response
}
};
xhr.onprogress = function(event) {
if (event.lengthComputable) {
alert(`Received ${event.loaded} of ${event.total} bytes`);
} else {
alert(`Received ${event.loaded} bytes`); // no Content-Length
}
};
xhr.onerror = function() {
alert("Request failed");
};
伺服器回應後,我們可以在下列 xhr 屬性中接收結果
status- HTTP 狀態碼(數字):
200、404、403等,在非 HTTP 失敗時可能是0。 statusText- HTTP 狀態訊息(字串):通常
200為OK、404為Not Found、403為Forbidden等。 response(舊腳本可能使用responseText)- 伺服器回應主體。
我們也可以使用對應屬性指定逾時
xhr.timeout = 10000; // timeout in ms, 10 seconds
如果要求在指定時間內未成功,則會取消要求並觸發 timeout 事件。
若要將參數新增至 URL,例如 ?name=value,並確保正確編碼,我們可以使用 URL 物件
let url = new URL('https://google.com/search');
url.searchParams.set('q', 'test me!');
// the parameter 'q' is encoded
xhr.open('GET', url); // https://google.com/search?q=test+me%21
回應類型
我們可以使用 xhr.responseType 屬性來設定回應格式
""(預設)– 取得為字串,"text"– 取得為字串,"arraybuffer"– 取得為ArrayBuffer(用於二進位資料,請參閱章節 ArrayBuffer、二進位陣列),"blob"– 取得為Blob(用於二進位資料,請參閱章節 Blob),"document"– 取得為 XML 文件(可以使用 XPath 和其他 XML 方法)或 HTML 文件(根據接收資料的 MIME 類型),"json"– 取得為 JSON(自動剖析)。
例如,讓我們取得回應為 JSON
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/example/json');
xhr.responseType = 'json';
xhr.send();
// the response is {"message": "Hello, world!"}
xhr.onload = function() {
let responseObj = xhr.response;
alert(responseObj.message); // Hello, world!
};
在舊腳本中,您也可能找到 xhr.responseText 甚至 xhr.responseXML 屬性。
它們存在於歷史原因,用於取得字串或 XML 文件。現在,我們應該在 xhr.responseType 中設定格式,並取得 xhr.response,如上所示。
準備狀態
XMLHttpRequest 會隨著進度而改變狀態。目前的狀態可作為 xhr.readyState 存取。
所有狀態,如 規格 中所述
UNSENT = 0; // initial state
OPENED = 1; // open called
HEADERS_RECEIVED = 2; // response headers received
LOADING = 3; // response is loading (a data packet is received)
DONE = 4; // request complete
XMLHttpRequest 物件會按順序移動它們:0 → 1 → 2 → 3 → … → 3 → 4。每次透過網路接收資料封包時,狀態 3 會重複。
我們可以使用 readystatechange 事件追蹤它們
xhr.onreadystatechange = function() {
if (xhr.readyState == 3) {
// loading
}
if (xhr.readyState == 4) {
// request finished
}
};
您可以在非常舊的程式碼中找到 readystatechange 監聽器,它存在於歷史原因,因為曾經有一段時間沒有 load 和其他事件。現在,load/error/progress 處理常式已將其棄用。
中止要求
我們可以在任何時候終止要求。呼叫 xhr.abort() 會執行此動作
xhr.abort(); // terminate the request
這會觸發 abort 事件,而且 xhr.status 會變成 0。
同步要求
如果在 open 方法中將第三個參數 async 設定為 false,則要求會同步執行。
換句話說,JavaScript 執行會在 send() 暫停,並在收到回應時繼續執行。有點像 alert 或 prompt 指令。
以下是改寫的範例,open 的第三個參數為 false
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/hello.txt', false);
try {
xhr.send();
if (xhr.status != 200) {
alert(`Error ${xhr.status}: ${xhr.statusText}`);
} else {
alert(xhr.response);
}
} catch(err) { // instead of onerror
alert("Request failed");
}
這看起來可能不錯,但同步呼叫很少使用,因為它們會在載入完成前封鎖頁面 JavaScript。在某些瀏覽器中,這會導致無法捲動。如果同步呼叫花費太多時間,瀏覽器可能會建議關閉「當機」的網頁。
XMLHttpRequest 的許多進階功能,例如從其他網域要求或指定逾時,都無法用於同步要求。此外,如您所見,沒有進度指示。
由於上述所有原因,同步要求使用得非常少,幾乎從不使用。我們將不再討論它們。
HTTP 標頭
XMLHttpRequest 允許同時傳送自訂標頭和從回應中讀取標頭。
有 3 種 HTTP 標頭方法
setRequestHeader(name, value)-
設定具有給定
name和value的要求標頭。例如
xhr.setRequestHeader('Content-Type', 'application/json');標頭限制有幾個標頭由瀏覽器獨家管理,例如
Referer和Host。完整清單 在規格中。為了使用者的安全和要求的正確性,
XMLHttpRequest不允許變更它們。無法移除標頭XMLHttpRequest的另一個特殊性是無法取消setRequestHeader。一旦設定標頭,就設定了。其他呼叫會將資訊新增到標頭,而不是覆寫它。
例如
xhr.setRequestHeader('X-Auth', '123'); xhr.setRequestHeader('X-Auth', '456'); // the header will be: // X-Auth: 123, 456 getResponseHeader(name)-
取得具有給定
name的回應標頭(Set-Cookie和Set-Cookie2除外)。例如
xhr.getResponseHeader('Content-Type') getAllResponseHeaders()-
傳回所有回應標頭,
Set-Cookie和Set-Cookie2除外。標頭以單行傳回,例如
Cache-Control: max-age=31536000 Content-Length: 4260 Content-Type: image/png Date: Sat, 08 Sep 2012 16:53:16 GMT標頭之間的換行符號永遠是
"\r\n"(與作業系統無關),因此我們可以輕鬆地將其拆分成個別標頭。名稱和值之間的分隔符號永遠是冒號後接空格": "。這在規格中是固定的。因此,如果我們想要取得具有名稱/值對的物件,我們需要加入一些 JS。
像這樣(假設如果兩個標頭具有相同名稱,則後者會覆寫前者)
let headers = xhr .getAllResponseHeaders() .split('\r\n') .reduce((result, current) => { let [name, value] = current.split(': '); result[name] = value; return result; }, {}); // headers['Content-Type'] = 'image/png'
POST、FormData
若要發出 POST 要求,我們可以使用內建的 FormData 物件。
語法
let formData = new FormData([form]); // creates an object, optionally fill from <form>
formData.append(name, value); // appends a field
我們建立它,選擇性地從表單填入,視需要附加更多欄位,然後
xhr.open('POST', ...)– 使用POST方法。xhr.send(formData)將表單提交至伺服器。
例如
<form name="person">
<input name="name" value="John">
<input name="surname" value="Smith">
</form>
<script>
// pre-fill FormData from the form
let formData = new FormData(document.forms.person);
// add one more field
formData.append("middle", "Lee");
// send it out
let xhr = new XMLHttpRequest();
xhr.open("POST", "/article/xmlhttprequest/post/user");
xhr.send(formData);
xhr.onload = () => alert(xhr.response);
</script>
表單會使用 multipart/form-data 編碼傳送。
或者,如果我們比較喜歡 JSON,則 JSON.stringify 並以字串傳送。
別忘了設定標頭 Content-Type: application/json,許多伺服器端架構會自動使用它來解碼 JSON
let xhr = new XMLHttpRequest();
let json = JSON.stringify({
name: "John",
surname: "Smith"
});
xhr.open("POST", '/submit')
xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
xhr.send(json);
.send(body) 方法相當雜食性。它可以傳送幾乎任何 body,包括 Blob 和 BufferSource 物件。
上傳進度
progress 事件僅會在下載階段觸發。
也就是說:如果我們 POST 了什麼,XMLHttpRequest 會先上傳我們的資料(要求主體),然後下載回應。
如果我們上傳了很大的東西,那麼我們肯定更想追蹤上傳進度。但 xhr.onprogress 在這裡幫不上忙。
還有另一個物件,沒有方法,專門用來追蹤上傳事件:xhr.upload。
它會產生事件,類似於 xhr,但 xhr.upload 僅在上傳時觸發它們
loadstart– 上傳開始。progress– 在上傳過程中會定期觸發。abort– 上傳中斷。error– 非 HTTP 錯誤。load– 上傳成功完成。timeout– 上傳逾時(如果設定了timeout屬性)。loadend– 上傳完成,成功或錯誤。
處理常式範例
xhr.upload.onprogress = function(event) {
alert(`Uploaded ${event.loaded} of ${event.total} bytes`);
};
xhr.upload.onload = function() {
alert(`Upload finished successfully.`);
};
xhr.upload.onerror = function() {
alert(`Error during the upload: ${xhr.status}`);
};
以下是一個實際範例:帶進度指示的上傳檔案
<input type="file" onchange="upload(this.files[0])">
<script>
function upload(file) {
let xhr = new XMLHttpRequest();
// track upload progress
xhr.upload.onprogress = function(event) {
console.log(`Uploaded ${event.loaded} of ${event.total}`);
};
// track completion: both successful or not
xhr.onloadend = function() {
if (xhr.status == 200) {
console.log("success");
} else {
console.log("error " + this.status);
}
};
xhr.open("POST", "/article/xmlhttprequest/post/upload");
xhr.send(file);
}
</script>
跨來源要求
XMLHttpRequest 可以發出跨來源要求,使用與 fetch 相同的 CORS 政策。
就像 fetch 一樣,它預設不會將 cookie 和 HTTP 授權傳送至其他來源。若要啟用它們,請將 xhr.withCredentials 設為 true
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('POST', 'http://anywhere.com/request');
...
請參閱章節 Fetch:跨來源要求 以取得有關跨來源標頭的詳細資訊。
摘要
使用 XMLHttpRequest 的 GET 要求的典型程式碼
let xhr = new XMLHttpRequest();
xhr.open('GET', '/my/url');
xhr.send();
xhr.onload = function() {
if (xhr.status != 200) { // HTTP error?
// handle error
alert( 'Error: ' + xhr.status);
return;
}
// get the response from xhr.response
};
xhr.onprogress = function(event) {
// report progress
alert(`Loaded ${event.loaded} of ${event.total}`);
};
xhr.onerror = function() {
// handle non-HTTP error (e.g. network down)
};
實際上還有更多事件,現代規格 列出了它們(按生命週期順序)
loadstart– 要求已開始。progress– 已經收到回應的資料封包,目前整個回應主體都在response中。abort– 要求已由呼叫xhr.abort()取消。error– 發生連線錯誤,例如錯誤的網域名稱。不會發生在 HTTP 錯誤(例如 404)上。load– 請求已成功完成。timeout– 請求因逾時而取消(僅在設定逾時時發生)。loadend– 在load、error、timeout或abort之後觸發。
error、abort、timeout 和 load 事件互斥。只會發生其中一個事件。
最常用的事件是載入完成 (load)、載入失敗 (error),或者我們可以使用單一的 loadend 處理常式並檢查請求物件 xhr 的屬性以查看發生了什麼事。
我們已經看過另一個事件:readystatechange。在規格確定之前,它很早以前就出現了。現在,我們不需要使用它,我們可以用更新的事件取代它,但它經常可以在舊腳本中找到。
如果我們需要特別追蹤上傳,那麼我們應該在 xhr.upload 物件上聆聽相同的事件。
留言
<code>標籤,對於多行 - 將它們包裝在<pre>標籤中,對於超過 10 行 - 使用沙盒 (plnkr,jsbin,codepen…)