W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
JavaScript 可以將網(wǎng)絡(luò)請求發(fā)送到服務(wù)器,并在需要時加載新信息。
例如,我們可以使用網(wǎng)絡(luò)請求來:
……所有這些都沒有重新加載頁面!
對于來自 JavaScript 的網(wǎng)絡(luò)請求,有一個總稱術(shù)語 “AJAX”(Asynchronous JavaScript And XML 的簡稱)。但是,我們不必使用 XML:這個術(shù)語誕生于很久以前,所以這個詞一直在那兒。
有很多方式可以向服務(wù)器發(fā)送網(wǎng)絡(luò)請求,并從服務(wù)器獲取信息。
fetch()
方法是一種現(xiàn)代通用的方法,那么我們就從它開始吧。舊版本的瀏覽器不支持它(可以 polyfill),但是它在現(xiàn)代瀏覽器中的支持情況很好。
基本語法:
let promise = fetch(url, [options])
url
? —— 要訪問的 URL。options
? —— 可選參數(shù):method,header 等。沒有 options
,這就是一個簡單的 GET 請求,下載 url
的內(nèi)容。
瀏覽器立即啟動請求,并返回一個該調(diào)用代碼應(yīng)該用來獲取結(jié)果的 promise
。
獲取響應(yīng)通常需要經(jīng)過兩個階段。
第一階段,當(dāng)服務(wù)器發(fā)送了響應(yīng)頭(response header),fetch
返回的 promise
就使用內(nèi)建的 Response class 對象來對響應(yīng)頭進行解析。
在這個階段,我們可以通過檢查響應(yīng)頭,來檢查 HTTP 狀態(tài)以確定請求是否成功,當(dāng)前還沒有響應(yīng)體(response body)。
如果 fetch
無法建立一個 HTTP 請求,例如網(wǎng)絡(luò)問題,亦或是請求的網(wǎng)址不存在,那么 promise 就會 reject。異常的 HTTP 狀態(tài),例如 404 或 500,不會導(dǎo)致出現(xiàn) error。
我們可以在 response 的屬性中看到 HTTP 狀態(tài):
status
? —— HTTP 狀態(tài)碼,例如 200。ok
? —— 布爾值,如果 HTTP 狀態(tài)碼為 200-299,則為 ?true
?。例如:
let response = await fetch(url);
if (response.ok) { // 如果 HTTP 狀態(tài)碼為 200-299
// 獲取 response body(此方法會在下面解釋)
let json = await response.json();
} else {
alert("HTTP-Error: " + response.status);
}
第二階段,為了獲取 response body,我們需要使用一個其他的方法調(diào)用。
Response
提供了多種基于 promise 的方法,來以不同的格式訪問 body:
response.text()
? —— 讀取 response,并以文本形式返回 response,response.json()
? —— 將 response 解析為 JSON 格式,response.formData()
? —— 以 ?FormData
? 對象(在 下一章 有解釋)的形式返回 response,response.blob()
? —— 以 Blob(具有類型的二進制數(shù)據(jù))形式返回 response,response.arrayBuffer()
? —— 以 ArrayBuffer(低級別的二進制數(shù)據(jù))形式返回 response,response.body
? 是 ReadableStream 對象,它允許你逐塊讀取 body,我們稍后會用一個例子解釋它。例如,我們從 GitHub 獲取最新 commits 的 JSON 對象:
let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits';
let response = await fetch(url);
let commits = await response.json(); // 讀取 response body,并將其解析為 JSON 格式
alert(commits[0].author.login);
也可以使用純 promise 語法,不使用 await
:
fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits')
.then(response => response.json())
.then(commits => alert(commits[0].author.login));
要獲取響應(yīng)文本,可以使用 await response.text()
代替 .json()
:
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
let text = await response.text(); // 將 response body 讀取為文本
alert(text.slice(0, 80) + '...');
作為一個讀取為二進制格式的演示示例,讓我們 fetch 并顯示一張 “fetch” 規(guī)范 中的圖片(Blob
操作的有關(guān)內(nèi)容請見 Blob):
let response = await fetch('/article/fetch/logo-fetch.svg');
let blob = await response.blob(); // 下載為 Blob 對象
// 為其創(chuàng)建一個 <img>
let img = document.createElement('img');
img.style = 'position:fixed;top:10px;left:10px;width:100px';
document.body.append(img);
// 顯示它
img.src = URL.createObjectURL(blob);
setTimeout(() => { // 3 秒后將其隱藏
img.remove();
URL.revokeObjectURL(img.src);
}, 3000);
重要:
我們只能選擇一種讀取 body 的方法。
如果我們已經(jīng)使用了
response.text()
方法來獲取 response,那么如果再用response.json()
,則不會生效,因為 body 內(nèi)容已經(jīng)被處理過了。
let text = await response.text(); // response body 被處理了 let parsed = await response.json(); // 失?。ㄒ呀?jīng)被處理過了)
Response header 位于 response.headers
中的一個類似于 Map 的 header 對象。
它不是真正的 Map,但是它具有類似的方法,我們可以按名稱(name)獲取各個 header,或迭代它們:
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
// 獲取一個 header
alert(response.headers.get('Content-Type')); // application/json; charset=utf-8
// 迭代所有 header
for (let [key, value] of response.headers) {
alert(`${key} = ${value}`);
}
要在 fetch
中設(shè)置 request header,我們可以使用 headers
選項。它有一個帶有輸出 header 的對象,如下所示:
let response = fetch(protectedUrl, {
headers: {
Authentication: 'secret'
}
});
……但是有一些我們無法設(shè)置的 header(詳見 forbidden HTTP headers):
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-*
?這些 header 保證了 HTTP 的正確性和安全性,所以它們僅由瀏覽器控制。
要創(chuàng)建一個 POST
請求,或者其他方法的請求,我們需要使用 fetch
選項:
method
? —— HTTP 方法,例如 ?POST
?,body
? —— request body,其中之一:FormData
? 對象,以 ?multipart/form-data
? 形式發(fā)送數(shù)據(jù),Blob
?/?BufferSource
? 發(fā)送二進制數(shù)據(jù),x-www-form-urlencoded
? 編碼形式發(fā)送數(shù)據(jù),很少使用。JSON 形式是最常用的。
例如,下面這段代碼以 JSON 形式發(fā)送 ?user
? 對象:
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
會默認(rèn)設(shè)置為 text/plain;charset=UTF-8
。
但是,當(dāng)我們要發(fā)送 JSON 時,我們會使用 headers
選項來發(fā)送 application/json
,這是 JSON 編碼的數(shù)據(jù)的正確的 Content-Type
。
我們同樣可以使用 Blob
或 BufferSource
對象通過 fetch
提交二進制數(shù)據(jù)。
例如,這里有一個 <canvas>
,我們可以通過在其上移動鼠標(biāo)來進行繪制。點擊 “submit” 按鈕將圖片發(fā)送到服務(wù)器:
<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
});
// 服務(wù)器給出確認(rèn)信息和圖片大小作為響應(yīng)
let result = await response.json();
alert(result.message);
}
</script>
</body>
請注意,這里我們沒有手動設(shè)置 Content-Type
header,因為 Blob
對象具有內(nèi)建的類型(這里是 image/png
,通過 toBlob
生成的)。對于 Blob
對象,這個類型就變成了 Content-Type
的值。
可以在不使用 async/await
的情況下重寫 submit()
函數(shù),像這樣:
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
? 調(diào)用組成:
let response = await fetch(url, options); // 解析 response header
let result = await response.json(); // 將 body 讀取為 json
或者以 promise 形式:
fetch(url, options)
.then(response => response.json())
.then(result => /* process result */)
響應(yīng)的屬性:
response.status
? —— response 的 HTTP 狀態(tài)碼,response.ok
? —— HTTP 狀態(tài)碼為 200-299,則為 ?true
?。response.headers
? —— 類似于 Map 的帶有 HTTP header 的對象。獲取 response body 的方法:
response.text()
? —— 讀取 response,并以文本形式返回 response,response.json()
? —— 將 response 解析為 JSON 對象形式,response.formData()
? —— 以 ?FormData
? 對象(?multipart/form-data
? 編碼,參見下一章)的形式返回 response,response.blob()
? —— 以 Blob(具有類型的二進制數(shù)據(jù))形式返回 response,response.arrayBuffer()
? —— 以 ArrayBuffer(低級別的二進制數(shù)據(jù))形式返回 response。到目前為止我們了解到的 fetch 選項:
method
? —— HTTP 方法,headers
? —— 具有 request header 的對象(不是所有 header 都是被允許的)body
? —— 要以 ?string
?,?FormData
?,?BufferSource
?,?Blob
? 或 ?UrlSearchParams
? 對象的形式發(fā)送的數(shù)據(jù)(request body)。在下一章,我們將會看到更多 ?fetch
? 的選項和用例。
創(chuàng)建一個異步函數(shù) getUsers(names)
,該函數(shù)接受 GitHub 登錄名數(shù)組作為輸入,查詢 GitHub 以獲取有關(guān)這些用戶的信息,并返回 GitHub 用戶數(shù)組。
帶有給定 USERNAME
的用戶信息的 GitHub 網(wǎng)址是:https://api.github.com/users/USERNAME
。
沙箱中有一個測試用例。
重要的細(xì)節(jié):
fetch
? 請求。null
? 到結(jié)果數(shù)組中。要獲取一個用戶,我們需要:fetch('https://api.github.com/users/USERNAME')
.
如果響應(yīng)的狀態(tài)碼是 200
,則調(diào)用 .json()
來讀取 JS 對象。
否則,如果 fetch
失敗,或者響應(yīng)的狀態(tài)碼不是 200
,我們只需要向結(jié)果數(shù)組返回 null
即可。
代碼如下:
async function getUsers(names) {
let jobs = [];
for(let name of names) {
let job = fetch(`https://api.github.com/users/${name}`).then(
successResponse => {
if (successResponse.status != 200) {
return null;
} else {
return successResponse.json();
}
},
failResponse => {
return null;
}
);
jobs.push(job);
}
let results = await Promise.all(jobs);
return results;
}
請注意:.then
調(diào)用緊跟在 fetch
后面,這樣,當(dāng)我們收到響應(yīng)時,它不會等待其他的 fetch,而是立即開始讀取 .json()
。
如果我們使用 await Promise.all(names.map(name => fetch(...)))
,并在 results
上調(diào)用 .json()
方法,那么它將會等到所有 fetch 都獲取到響應(yīng)數(shù)據(jù)才開始解析。通過將 .json()
直接添加到每個 fetch
中,我們就能確保每個 fetch 在收到響應(yīng)時都會立即開始以 JSON 格式讀取數(shù)據(jù),而不會彼此等待。
這個例子表明,即使我們主要使用 async/await
,低級別的 Promise API 仍然很有用。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: