JavaScript 可以向伺服器發送網路請求,並在需要時載入新的資訊。
例如,我們可以使用網路請求來
- 提交訂單,
- 載入使用者資訊,
- 接收伺服器發送的最新更新,
- …等等。
…而且無需重新載入網頁!
有一個統稱「AJAX」(簡稱Asynchronous JavaScript And XML)用於表示 JavaScript 的網路請求。不過我們不必使用 XML:這個術語來自於舊時代,這就是為什麼會有這個字詞。你可能已經聽過這個術語了。
有多種方法可以發送網路請求並從伺服器取得資訊。
fetch()
方法既現代又多功能,因此我們將從此開始。舊瀏覽器不支援此方法(可以使用 polyfill),但現代瀏覽器支援得很好。
基本語法為
let promise = fetch(url, [options])
url
– 要存取的 URL。options
– 選擇性參數:方法、標頭等。
若沒有 options
,這會是一個簡單的 GET 要求,下載 url
的內容。
瀏覽器會立即開始要求,並傳回一個承諾,呼叫程式碼應使用該承諾來取得結果。
取得回應通常是一個兩階段的流程。
首先,fetch
傳回的 promise
會在伺服器回應標頭時,使用內建 Response 類別的物件來解析。
在此階段,我們可以檢查 HTTP 狀態,查看是否成功,檢查標頭,但還沒有主體。
如果 fetch
無法進行 HTTP 要求(例如網路問題或沒有此網站),承諾會被拒絕。異常的 HTTP 狀態,例如 404 或 500,不會造成錯誤。
我們可以在回應屬性中看到 HTTP 狀態
status
– HTTP 狀態碼,例如 200。ok
– 布林值,如果 HTTP 狀態碼為 200-299,則為true
。
例如
let response = await fetch(url);
if (response.ok) { // if HTTP-status is 200-299
// get the response body (the method explained below)
let json = await response.json();
} else {
alert("HTTP-Error: " + response.status);
}
其次,要取得回應主體,我們需要使用額外的呼叫方法。
Response
提供多個基於承諾的方法,以各種格式存取主體
response.text()
– 讀取回應並傳回為文字,response.json()
– 將回應解析為 JSON,response.formData()
– 將回應傳回為FormData
物件(在 下一章 中說明),response.blob()
– 將回應傳回為 Blob(帶有類型的二進位資料),response.arrayBuffer()
– 將回應傳回為 ArrayBuffer(二進位資料的低階表示),- 此外,
response.body
是 ReadableStream 物件,它允許您逐塊讀取主體,我們稍後會看到一個範例。
例如,讓我們從 GitHub 取得一個包含最新提交的 JSON 物件
let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits';
let response = await fetch(url);
let commits = await response.json(); // read response body and parse as JSON
alert(commits[0].author.login);
或者,使用純粹的承諾語法,不使用 await
fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits')
.then(response => response.json())
.then(commits => alert(commits[0].author.login));
要取得回應文字,請使用 await response.text()
,而非 .json()
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
let text = await response.text(); // read response body as text
alert(text.slice(0, 80) + '...');
作為二進位格式讀取的展示範例,我們來擷取並顯示 Blob 章節)
let response = await fetch('/article/fetch/logo-fetch.svg');
let blob = await response.blob(); // download as Blob object
// create <img> for it
let img = document.createElement('img');
img.style = 'position:fixed;top:10px;left:10px;width:100px';
document.body.append(img);
// show it
img.src = URL.createObjectURL(blob);
setTimeout(() => { // hide after three seconds
img.remove();
URL.revokeObjectURL(img.src);
}, 3000);
我們只能選擇一種主體讀取方法。
如果我們已使用 response.text()
取得回應,則 response.json()
將無法運作,因為主體內容已處理完畢。
let text = await response.text(); // response body consumed
let parsed = await response.json(); // fails (already consumed)
回應標頭
回應標頭在 response.headers
中以類似 Map 的標頭物件提供。
它並非完全是 Map,但它具有類似的方法,可依名稱取得個別標頭或反覆處理它們
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
// get one header
alert(response.headers.get('Content-Type')); // application/json; charset=utf-8
// iterate over all headers
for (let [key, value] of response.headers) {
alert(`${key} = ${value}`);
}
要求標頭
若要在 fetch
中設定要求標頭,我們可以使用 headers
選項。它有一個包含傳出標頭的物件,如下所示
let response = fetch(protectedUrl, {
headers: {
Authentication: 'secret'
}
});
…不過,有一份 禁止的 HTTP 標頭 清單,我們無法設定
Accept-Charset
、Accept-Encoding
Access-Control-Request-Headers
Access-Control-Request-Method
Connection
Content-Length
Cookie
、Cookie2
Date
DNT
Expect
Host
Keep-Alive
Origin
Referer
TE
Trailer
Transfer-Encoding
Upgrade
Via
Proxy-*
Sec-*
這些標頭可確保 HTTP 正確且安全,因此它們由瀏覽器獨家控制。
POST 要求
若要執行 POST
要求或使用其他方法的請求,我們需要使用 fetch
選項
method
– HTTP 方法,例如POST
,body
– 請求主體,其中之一為- 字串(例如 JSON 編碼),
FormData
物件,以multipart/form-data
提交資料,Blob
/BufferSource
,用於傳送二進位資料,- URLSearchParams,用於以
x-www-form-urlencoded
編碼提交資料,很少使用。
大多數時候都使用 JSON 格式。
例如,以下程式碼會將 user
物件提交為 JSON
let user = {
name: 'John',
surname: 'Smith'
};
let response = await fetch('/article/fetch/post/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(user)
});
let result = await response.json();
alert(result.message);
請注意,如果請求 body
是字串,則 Content-Type
標頭預設設定為 text/plain;charset=UTF-8
。
不過,由於我們要傳送 JSON,因此我們使用 headers
選項來傳送 application/json
,這是 JSON 編碼資料正確的 Content-Type
。
傳送圖片
我們也可以使用 Blob
或 BufferSource
物件,透過 fetch
傳送二進制資料。
在這個範例中,有一個 <canvas>
,我們可以在上面移動滑鼠來繪圖。點選「提交」按鈕會將圖片傳送至伺服器
<body style="margin:0">
<canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>
<input type="button" value="Submit" onclick="submit()">
<script>
canvasElem.onmousemove = function(e) {
let ctx = canvasElem.getContext('2d');
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
};
async function submit() {
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
let response = await fetch('/article/fetch/post/image', {
method: 'POST',
body: blob
});
// the server responds with confirmation and the image size
let result = await response.json();
alert(result.message);
}
</script>
</body>
請注意,這裡我們沒有手動設定 Content-Type
標頭,因為 Blob
物件有一個內建類型(這裡是 image/png
,由 toBlob
產生)。對於 Blob
物件,該類型會變成 Content-Type
的值。
submit()
函式可以改寫成不使用 async/await
的形式,如下所示
function submit() {
canvasElem.toBlob(function(blob) {
fetch('/article/fetch/post/image', {
method: 'POST',
body: blob
})
.then(response => response.json())
.then(result => alert(JSON.stringify(result, null, 2)))
}, 'image/png');
}
摘要
一個典型的 fetch 要求包含兩個 await
呼叫
let response = await fetch(url, options); // resolves with response headers
let result = await response.json(); // read body as json
或者,不使用 await
fetch(url, options)
.then(response => response.json())
.then(result => /* process result */)
回應屬性
response.status
– 回應的 HTTP 程式碼,response.ok
– 如果狀態是 200-299,則為true
。response.headers
– 包含 HTTP 標頭的類似 Map 的物件。
取得回應主體的方法
response.text()
– 將回應傳回為文字,response.json()
– 將回應解析為 JSON 物件,response.formData()
– 將回應傳回為FormData
物件(multipart/form-data
編碼,請參閱下一章),response.blob()
– 將回應傳回為 Blob(帶有類型的二進位資料),response.arrayBuffer()
– 將回應傳回為 ArrayBuffer(低階二進制資料),
目前為止的 Fetch 選項
method
– HTTP 方法,headers
– 包含要求標頭的物件(並非任何標頭都允許),body
– 要傳送的資料(要求主體),可以是string
、FormData
、BufferSource
、Blob
或UrlSearchParams
物件。
在下一章中,我們將看到更多 fetch
的選項和使用案例。
留言
<code>
標籤,對於多行,請將其包覆在<pre>
標籤中,對於超過 10 行,請使用沙盒 (plnkr、jsbin、codepen…)