XMLHttpRequest
是內建於瀏覽器的物件,允許在 JavaScript 中發出 HTTP 請求。
儘管名稱中有「XML」,但它可以處理任何資料,不只 XML 格式。我們可以上傳/下載檔案、追蹤進度等等。
目前有另一個較新的方法 fetch
,在某種程度上取代了 XMLHttpRequest
。
在現代網頁開發中,XMLHttpRequest
因以下三個原因而被使用
- 歷史原因:我們需要支援現有的
XMLHttpRequest
腳本。 - 我們需要支援舊瀏覽器,而且不想要多重填充(例如,保持腳本精簡)。
- 我們需要
fetch
目前無法執行的功能,例如追蹤上傳進度。
聽起來很熟悉嗎?如果是,那麼沒問題,請繼續使用 XMLHttpRequest
。否則,請前往 Fetch。
基礎知識
XMLHttpRequest 有兩種操作模式:同步和非同步。
讓我們先看看非同步,因為它在大部分情況下都被使用。
要執行請求,我們需要 3 個步驟
-
建立
XMLHttpRequest
let 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…)