etch 是 XMLHttpRequest 的升級(jí)版,使用js腳本發(fā)出網(wǎng)絡(luò)請(qǐng)求,但是與 XMLHttpRequest 不同的是,fetch 方式使用 Promise,相比 XMLHttpRequest 更加簡(jiǎn)潔。所以我們告別XMLHttpRequest,引入 fetch 如何使用?
fetch() 是一個(gè)全局方法,提供一種簡(jiǎn)單,合理的方式跨網(wǎng)絡(luò)獲取資源。它的請(qǐng)求是基于 Promise 的,需要詳細(xì)學(xué)習(xí) Promise ,請(qǐng)點(diǎn)擊《 Promise詳解 》。它是專門為了取代傳統(tǒng)的 xhr 而生的。
1.1、fetch使用語(yǔ)法
fetch(url,options).then((response)=>{
//處理http響應(yīng)
},(error)=>{
//處理錯(cuò)誤
})
url :是發(fā)送網(wǎng)絡(luò)請(qǐng)求的地址。
options:發(fā)送請(qǐng)求參數(shù),
1.2、response 對(duì)象
fetch 請(qǐng)求成功后,響應(yīng) response 對(duì)象如圖:
1.3、讀取內(nèi)容方法
response 對(duì)象根據(jù)服務(wù)器返回的不同類型數(shù)據(jù),提供了不同的讀取方法。分別有:
上述 5 個(gè)方法,返回的都是 promise 對(duì)象,必須等到異步操作結(jié)束,才能得到服務(wù)器返回的完整數(shù)據(jù)。
1.4、response.clone()
stream 對(duì)象只能讀取一次,讀取完就沒了,這意味著,上邊的五種讀取方法,只能使用一個(gè),否則會(huì)報(bào)錯(cuò)。
因此 response 對(duì)象提供了 clone() 方法,創(chuàng)建 respons 對(duì)象副本,實(shí)現(xiàn)多次讀取。如下:將一張圖片,讀取兩次:
const response1 = await fetch('flowers.jpg');
const response2 = response1.clone();
const myBlob1 = await response1.blob();
const myBlob2 = await response2.blob();
image1.src = URL.createObjectURL(myBlob1);
image2.src = URL.createObjectURL(myBlob2);
1.4、response.body()
body 屬性返回一個(gè) ReadableStream 對(duì)象,供用戶操作,可以用來(lái)分塊讀取內(nèi)容,顯示下載的進(jìn)度就是其中一種應(yīng)用。
const response = await fetch('flower.jpg');
const reader = response.body.getReader();
while(true) {
const {done, value} = await reader.read();
if (done) {
break;
}
console.log(`Received ${value.length} bytes`)
}
response.body.getReader() 返回一個(gè)遍歷器,這個(gè)遍歷器 read() 方法每次都會(huì)返回一個(gè)對(duì)象,表示本次讀取的內(nèi)容塊。
請(qǐng)求方式不同,傳值方式也不同。xhr 會(huì)分別處理 get 和 post 數(shù)據(jù)傳輸,還有請(qǐng)求頭設(shè)置,同樣 fetch 也需要分別處理。
2.1、get 方式
只需要在url中加入傳輸數(shù)據(jù),options中加入請(qǐng)求方式。如下面代碼所示:
<input type="text" id="user"><br>
<input type="password" id="pas"><br>
<button onclick="login()">提交</button>
<script>
function login(){
fetch(`http://localhost:80/fetch.html?user=${user.value}&pas=${pas.value}`,{
method:'GET'
}).then(response=>{
console.log('響應(yīng)',response)
})
}
</script>
2.2、post 方式
使用 post 發(fā)送請(qǐng)求時(shí),需要設(shè)置請(qǐng)求頭、請(qǐng)求數(shù)據(jù)等。
將上個(gè)實(shí)例,改寫成 post 方式提交數(shù)據(jù),代碼如下:
fetch(`http://localhost:80/ES6練習(xí)題/53fetch.html`,{
method:'POST',
headers:{
'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'
},
body:`user=${user.value}&pas=${pas.value}`
}).then(response=>{
console.log('響應(yīng)',response)
})
如果是提交json數(shù)據(jù)時(shí),需要把json轉(zhuǎn)換成字符串。如
body:JSON.stringify(json)
如果提交的是表單數(shù)據(jù),使用 formData轉(zhuǎn)化下,如:
body:new FormData(form)
上傳文件,可以包含在整個(gè)表單里一起提交,如:
const input = document.querySelector('input[type="file"]');
const data = new FormData();
data.append('file', input.files[0]);
data.append('user', 'foo');
fetch('/avatars', {
method: 'POST',
body: data
});
上傳二進(jìn)制數(shù)據(jù),將 bolb 或 arrayBuffer 數(shù)據(jù)放到body屬性里,如:
let blob = await new Promise(resolve =>
canvasElem.toBlob(resolve, 'image/png')
);
let response = await fetch('/article/fetch/post/image', {
method: 'POST',
body: blob
});
3.1、fetch兼容性
fetch原生支持率如圖:
fetch 是相對(duì)較新的技術(shù),IE瀏覽器不支持,還有其他低版本瀏覽器也不支持,因此如果使用fetch時(shí),需要考慮瀏覽器兼容問題。
解決辦法:引入 polyfill 完美支持 IE8 以上。
polyfill 的原理就是探測(cè)fetch是否支持,如果不支持則用 xhr 實(shí)現(xiàn)。支持 fetch 的瀏覽器,響應(yīng)中文會(huì)亂碼,所以使用 fetch-detector 和 fetch-ie8 解決亂碼問題。
3.2、fetch默認(rèn)不帶cookie
傳遞cookie時(shí),必須在header參數(shù)內(nèi)加上 credentials:'include',才會(huì)像 xhr 將當(dāng)前cookie 帶有請(qǐng)求中。
3.3、異常處理
fetch 不同于 xhr ,xhr 自帶取消、錯(cuò)誤等方法,所以服務(wù)器返回 4xx 或 5xx 時(shí),是不會(huì)拋出錯(cuò)誤的,需要手動(dòng)處理,通過 response 中的 status 字段來(lái)判斷。
avaScript 初學(xué)者學(xué)完語(yǔ)法,進(jìn)入實(shí)際的網(wǎng)頁(yè)編程,一定有人告訴你,要掌握一個(gè)叫做 XMLHttpRequest 的東西。
腳本都靠它發(fā)出 HTTP 請(qǐng)求,跟服務(wù)器通信。所謂的 AJAX 操作就是基于它實(shí)現(xiàn)的。要是沒有它,就不會(huì)有單頁(yè)應(yīng)用。
這個(gè) XMLHttpRequest 對(duì)象,不僅名字怪,用法也非常獨(dú)特。
let xhr = new XMLHttpRequest;
xhr.open('GET', url)
xhr.onload = function {
if (this.status === 200) {
let data = JSON.parse(this.responseText);
console.log(data);
}
};
xhr.onerror = function (err) {
console.log('Error Occurred :', err);
}
xhr.send;
就是因?yàn)閷懫饋?lái)麻煩,實(shí)際開發(fā)中,往往使用它的各種封裝庫(kù),比如早期的 jQuery。
$.get("test.php", function (data) {
$("body")
.append("Name: " + data.name)
.append("Time: " + data.time);
}, "json");
早期的封裝庫(kù)都使用回調(diào)函數(shù),很不直觀。后來(lái)的封裝庫(kù)都改用 Promise 的寫法,比如 axios。
axios.get('/user?ID=12345')
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
這樣用起來(lái),確實(shí)方便了,但是需要加載外部庫(kù),而它又是一個(gè)天天用到的基本需求。所以,瀏覽器廠商最終決定,拋棄 XMLHttpRequest,另起爐灶,重寫一套全新的 API,內(nèi)置在瀏覽器里面。
這就是 Fetch API,它使用fetch
發(fā)出請(qǐng)求,返回的是 Promise 對(duì)象。
fetch('https://api.github.com/users/ruanyf')
.then(response => response.json)
.then(json => console.log(json))
.catch(err => console.log('Request Failed', err));
Promise 可以使用 await 語(yǔ)法,表達(dá)起來(lái)跟同步操作一模一樣。
async function getJSON {
let url = 'https://api.github.com/users/ruanyf';
try {
let response = await fetch(url);
return await response.json;
} catch (error) {
console.log('Request Failed', error);
}
}
Fetch API 的優(yōu)點(diǎn)是寫法簡(jiǎn)單,缺點(diǎn)是這個(gè) API 包含的內(nèi)容非常多,想要完全掌握并不容易。
我一直想寫一篇教程,完整介紹它的各種用法,便于自己使用的時(shí)候查找。現(xiàn)在終于寫出來(lái),以后可以告別 XMLHttpRequest 了。
http://www.ruanyifeng.com/blog/2020/12/fetch-tutorial.html
(完)
etch()方法
ECMAscript官方繼續(xù)推出了一個(gè)基于Promise的請(qǐng)求方法, 更簡(jiǎn)單, 更便捷。
就是fetch()方法,這個(gè)方法可以替代傳統(tǒng)Ajax請(qǐng)求, 返回Promise的實(shí)例。
fetch()函數(shù)是ECMAscript提出的新的特性, 它:
能夠發(fā)出Ajax請(qǐng)求, 并且請(qǐng)求原理不是xhr, 而是新的原理;
天生可以跨域, 但是需要設(shè)置頭, 服務(wù)器可以寫腳本識(shí)別這個(gè)頭部判斷是否應(yīng)該拒絕它;
fetch()返回Promise對(duì)象, 所以可以用then和catch方法, then方法的第一個(gè)函數(shù)是resolve, 這個(gè)resolve的返回值將自動(dòng)被await等號(hào)左邊的變量的接收。
必須寫async和await。
fetch的語(yǔ)法
fetch(url)
.then(res => console.log(res))
.then(res => console.log(res))
.catch(err => console.log(err));
使用場(chǎng)景1
async function main(){
//請(qǐng)求回來(lái)的數(shù)據(jù)都存在body中,作為一種可讀的流返回,要使用json方法轉(zhuǎn)換
var data1 = await fetch("data/1.json").then(data=>data.json());
var data2 = await fetch("data/2.json").then(data=>data.json());
var data3 = await fetch("data/3.json").then(data=>data.json());
console.log(data1)
console.log(data2)
console.log(data3)
};
main();
使用場(chǎng)景2
// 請(qǐng)求JSON數(shù)據(jù)
async function getUsers(){
const res = await fetch("https://www.xxx.com");
const data = await res.json();
return data;
}
getUsers()
.then((users) => console.log(users))
.catch((err) => console.log(err))
可以不用jQuery了!傳統(tǒng)Ajax已死,fetch永生。
兼容性注意:
babel沒有任何插件可以翻譯fetch為傳統(tǒng)Ajax。
fetch只能用在瀏覽器環(huán)境, 不能用在Nodejs環(huán)境。
fetch請(qǐng)求的兩種數(shù)據(jù)格式
html結(jié)構(gòu):
<div>
<button id="btn1">請(qǐng)求本地文本數(shù)據(jù)</button>
<button id="btn2">請(qǐng)求網(wǎng)絡(luò)json數(shù)據(jù)</button>
<div id="output"></div>
</div>
JavaScript邏輯:
<script>
document.getElementById('btn1').addEventListener('click', getText);
document.getElementById('btn2').addEventListener('click', getJson);
// 獲取本地純文本數(shù)據(jù)
function getText(){
fetch("test.txt").then(res => res.text())
.then(data => {console.log(data)})
.catch(err => console.log(err));
}
// 獲取json數(shù)據(jù)
function getJson(){
fetch("posts.json")
.then(res => res.json())
.then(data => { console.log(data)})
.catch(err => console.log(err));
}
</script>
fetch方法的配置
fetch()方法的第二個(gè)參數(shù)是配置參數(shù), 可以配置發(fā)送POST/PUT/DELETE等請(qǐng)求類型。
fetch('url', [options]);
實(shí)踐中, post請(qǐng)求會(huì)像下面這樣:
fetch('url', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({name:'小明'})
})
.then(data=>{console.log(data)})
.catch(err=>{console.log(err)})
method請(qǐng)求類型參數(shù):
設(shè)置請(qǐng)求方式(如POST | PUT | DELETE), fetch默認(rèn)請(qǐng)求方式為GET。
headers請(qǐng)求頭參數(shù)
向服務(wù)器說明希望的到什么待遇, 這里主要用到Content-Type
因?yàn)橐话闶褂肑SON數(shù)據(jù)格式, 所以設(shè)置Content-Type為application/json
body參數(shù)設(shè)置報(bào)文體
要發(fā)送給后端的內(nèi)容, 如果發(fā)送JSON數(shù)據(jù), 必須調(diào)用JSON.stringify()方法。
credentials證書參數(shù)
因?yàn)槟J(rèn)情況下, fetch不會(huì)從服務(wù)端發(fā)送或接收任何cookies, 所以可以配置以下參數(shù)指定發(fā)送cookies的情況, 默認(rèn)值:same-origin, 以下為可選值:
omit: 不發(fā)送cookies
same-origin: 在同域情況下發(fā)送cookies
include: 同域或跨域都發(fā)送cookies
可以理解為:
跨域請(qǐng)求是否提供憑據(jù)信息(cookie、HTTP認(rèn)證及客戶端SSL證明等)
也可以簡(jiǎn)單的理解為, 當(dāng)前請(qǐng)求為跨域類型時(shí)是否在請(qǐng)求中協(xié)帶cookie。
基于Promise封裝fetch請(qǐng)求庫(kù)
用ES6的class類封裝一個(gè)函數(shù)
app.js
/*
* 基于Promise封裝fetch請(qǐng)求庫(kù)
*/
class FetchHttp{
// 封裝get請(qǐng)求
get(url){
return new Promise((resolve,reject)=>{
fetch(url)
.then(data => resolve(data.json()))
.catch(err => reject(err))
})
}
// 封裝post請(qǐng)求
post(url, data){
return new Promise((resolve, reject)=>{
fetch(url, {
method:"POST",
headers:{'Content-type':'application/json'},
body:JSON.stringify(data)
})
.then(data => resolve(data.json()))
.catch(err => reject(err))
})
}
// 封裝put請(qǐng)求
put(url, data){
return new Promise((resolve, reject)=>{
fetch(url, {
method:"PUT",
headers:{'Content-type':'application/json'},
body:JSON.stringify(data)
})
.then(data => resolve(data.json()))
.catch(err => reject(err))
})
}
// 封裝delete請(qǐng)求
delete(url){
return new Promise((resolve, reject)=>{
fetch(url, {
method:"DELETE",
headers:{'Content-type':'application/json'},
})
.then(data => resolve('數(shù)據(jù)刪除成功!'))
.catch(err => reject(err))
})
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
</body>
<script src="js/app.js"></script>
<script>
const http = new FetchHttp; //引入函數(shù)
const data = {
name:"小明",
sex:"男",
age:29
}
http.delete("https://www.xxx.com")
.then(data=>{console.log(data)})
.catch(err=>{console.log(err)})
</script>
</html>
基于async封裝fetch請(qǐng)求庫(kù)app.js
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。