Axios vs. fetch():哪個(gè)最適合 HTTP 請(qǐng)求?
因?yàn)锳xios的易于使用,所以有些開發(fā)人員比起內(nèi)置的API,更喜歡Axios。
但許多人高估了這個(gè)庫。
fetch() API不但完全能夠重現(xiàn)Axios的關(guān)鍵功能,而且還有隨時(shí)可用于所有現(xiàn)代瀏覽器中的獨(dú)特優(yōu)勢。
在本文中,我將按照基本語法、向后兼容性、響應(yīng)超時(shí)、自動(dòng)JSON數(shù)據(jù)轉(zhuǎn)換、HTTP攔截器、下載進(jìn)度、同時(shí)請(qǐng)求這些方面來比較fetch()和Axios,看看它們?nèi)绾螆?zhí)行任務(wù)。
希望在本文結(jié)束時(shí),大家對(duì)這兩個(gè)API有了更深入的了解。
基本語法
在我們深入研究Axios更高級(jí)地功能之前,先與fetch()進(jìn)行基本語法的比較。
下面是Axios如何將帶有自定義請(qǐng)求頭的[POST]請(qǐng)求發(fā)送到指定URL的代碼:
// axios
const url = 'https://jsonplaceholder.typicode.com/posts'
const data = {
a: 10,
b: 20,
};
axios
.post(url, data, {
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
})
.then(({data}) => {
console.log(data);
});與fetch()版本進(jìn)行比較:
// fetch()
const url = "https://jsonplaceholder.typicode.com/todos";
const options = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
body: JSON.stringify({
a: 10,
b: 20,
}),
};
fetch(url, options)
.then((response) => response.json())
.then((data) => {
console.log(data);
});注意:
- 為發(fā)送數(shù)據(jù),fetch()使用body屬性將數(shù)據(jù)發(fā)送到服務(wù)端,而Axios使用data屬性
- fetch()中的數(shù)據(jù)使用JSON.stringify方法轉(zhuǎn)換為字符串
- Axios自動(dòng)轉(zhuǎn)換從服務(wù)器返回的數(shù)據(jù),但使用fetch()時(shí),你必須調(diào)用response.json方法將數(shù)據(jù)解析為JavaScript對(duì)象。
- 使用Axios,服務(wù)器提供的數(shù)據(jù)響應(yīng)可以在數(shù)據(jù)對(duì)象中訪問,而對(duì)于fetch()方法,最終數(shù)據(jù)可以命名為任何變量
向后兼容性
Axios的主要賣點(diǎn)之一是其廣泛的瀏覽器支持。
即使是像IE11這樣的舊瀏覽器也可以毫無問題地運(yùn)行Axios。這是因?yàn)樗澈笫褂昧薠MLHttpRequest。
而fetch()僅支持Chrome 42+,F(xiàn)irefox 39+,Edge 14+和Safari 10.3+。
如果你使用Axios的唯一原因是向后兼容性,那么實(shí)際上并不需要HTTP庫。而且,你可以將fetch()與polyfill一起使用,在不支持fetch()的web瀏覽器上實(shí)現(xiàn)類似的功能。
要使用fetch() polyfill,可以通過npm命令進(jìn)行安裝,如下所示:
npm install whatwg-fetch --save然后,提出如下請(qǐng)求:
import 'whatwg-fetch'
window.fetch(...)謹(jǐn)記,在有些舊瀏覽器中,可能還需要promise polyfill。
響應(yīng)超時(shí)
在Axios中設(shè)置超時(shí)的簡單性,是一些開發(fā)人員比fetch()更喜歡Axios的原因之一。
在Axios中,你可以使用配置對(duì)象的timeout屬性來設(shè)置請(qǐng)求中止之前的毫秒數(shù)。
例如:
axios({
method: 'post',
url: '/login',
timeout: 4000, // 4 seconds timeout
data: {
firstName: 'David',
lastName: 'Pollock'
}
})
.then(response => {/* handle the response */})
.catch(error => console.error('timeout exceeded'))Fetch()通過AbortController接口提供類似的功能。
不過,它的代碼不如Axios版本簡單:
const controller = new AbortController();
const options = {
method: 'POST',
signal: controller.signal,
body: JSON.stringify({
firstName: 'David',
lastName: 'Pollock'
})
};
const promise = fetch('/login', options);
const timeoutId = setTimeout(() => controller.abort(), 4000);
promise
.then(response => {/* handle the response */})
.catch(error => console.error('timeout exceeded'));代碼使用AbortController.abort()構(gòu)造函數(shù)創(chuàng)建AbortController對(duì)象,它允許我們稍后中止請(qǐng)求。
Signal是AbortController的只讀屬性,提供了一種與請(qǐng)求通信或中止請(qǐng)求的方法。
如果服務(wù)器在4秒內(nèi)沒有響應(yīng),則調(diào)用controller.abort(),終止操作。
自動(dòng)JSON數(shù)據(jù)轉(zhuǎn)換
如前所述,Axios在發(fā)送請(qǐng)求時(shí)會(huì)自動(dòng)字符串化數(shù)據(jù)(當(dāng)然你也可以覆蓋默認(rèn)行為并定義不同的轉(zhuǎn)換機(jī)制)。
但是,當(dāng)使用fetch()時(shí),你必須手動(dòng)執(zhí)行此操作。
比較:
// axios
axios.get('https://api.github.com/orgs/axios')
.then(response => {
console.log(response.data);
}, error => {
console.log(error);
});// fetch()
fetch('https://api.github.com/orgs/axios')
.then(response => response.json()) // one extra step
.then(data => {
console.log(data)
})
.catch(error => console.error(error));自動(dòng)轉(zhuǎn)換數(shù)據(jù)是一個(gè)不錯(cuò)的功能,但同樣,這不是你不能用fetch()做的事情。
HTTP攔截器
Axios的主要功能之一是它能夠攔截HTTP請(qǐng)求。
當(dāng)你需要檢查或更改從應(yīng)用程序到服務(wù)器的HTTP請(qǐng)求時(shí),使用HTTP攔截器非常方便,從服務(wù)器到應(yīng)用程序亦是如此(例如,日志記錄、身份驗(yàn)證或重試失敗的HTTP請(qǐng)求)。
使用攔截器就不必為每個(gè)HTTP請(qǐng)求編寫單獨(dú)的代碼。
在你想要為處理請(qǐng)求和響應(yīng)設(shè)置全局策略時(shí),HTTP攔截器非常有用。
以下是在Axios中聲明請(qǐng)求攔截器的方法:
axios.interceptors.request.use(config => {
// log a message before any HTTP request is sent
console.log('Request was sent');
return config;
});
// sent a GET request
axios.get('https://api.github.com/users/sideshowbarker')
.then(response => {
console.log(response.data);
});上面的代碼中,axios.interceptors.request.use()方法用于定義發(fā)送HTTP請(qǐng)求之前要運(yùn)行的代碼。而axios.interceptors.response.use()用于攔截來自服務(wù)器的響應(yīng)。
假設(shè)存在網(wǎng)絡(luò)錯(cuò)誤,那么通過響應(yīng)偵聽器,可以重試相同的請(qǐng)求。
默認(rèn)情況下,fetch()不提供攔截請(qǐng)求的方法,但它的解決方法也并不復(fù)雜。
那就是覆蓋全局fetch()方法并定義自己的攔截器,如下所示:
fetch = (originalFetch => {
return (...arguments) => {
const result = originalFetch.apply(this, arguments);
return result.then(console.log('Request was sent'));
};
})(fetch);
fetch('https://api.github.com/orgs/axios')
.then(response => response.json())
.then(data => {
console.log(data)
});下載進(jìn)度
進(jìn)度條在加載時(shí)非常有用,尤其是對(duì)于互聯(lián)網(wǎng)速度較慢的用戶。
以前,JavaScript程序員使用XMLHttpRequest.onprogress回調(diào)處理程序來實(shí)現(xiàn)進(jìn)度指示器。
Fetch API沒有onprogress處理程序。事實(shí)上,它通過響應(yīng)對(duì)象的body屬性來提供ReadableStream的實(shí)例。
以下示例表明如何使用ReadableStream在圖像下載期間為用戶提供即時(shí)反饋:
index.html
<!-- Wherever you html is -->
<div id="progress" src="">progress</div>
<img id="img">
script.js
'use strict'
const element = document.getElementById('progress');
fetch('https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg')
.then(response => {
if (!response.ok) {
throw Error(response.status+' '+response.statusText)
}
// ensure ReadableStream is supported
if (!response.body) {
throw Error('ReadableStream not yet supported in this browser.')
}
// store the size of the entity-body, in bytes
const contentLength = response.headers.get('content-length');
// ensure contentLength is available
if (!contentLength) {
throw Error('Content-Length response header unavailable');
}
// parse the integer into a base-10 number
const total = parseInt(contentLength, 10);
let loaded = 0;
return new Response(
// create and return a readable stream
new ReadableStream({
start(controller) {
const reader = response.body.getReader();
read();
function read() {
reader.read().then(({done, value}) => {
if (done) {
controller.close();
return;
}
loaded += value.byteLength;
progress({loaded, total})
controller.enqueue(value);
read();
}).catch(error => {
console.error(error);
controller.error(error)
})
}
}
})
);
})
.then(response =>
// construct a blob from the data
response.blob()
)
.then(data => {
// insert the downloaded image into the page
document.getElementById('img').src = URL.createObjectURL(data);
})
.catch(error => {
console.error(error);
})
function progress({loaded, total}) {
element.innerHTML = Math.round(loaded/total*100)+'%';
}在Axios中實(shí)現(xiàn)進(jìn)度指示器更簡單,尤其是在使用Axios進(jìn)度條模塊時(shí)。
首先,包含以下樣式和腳本:
// the head of your HTML
<link rel="stylesheet" type="text/css"
/>
// the body of your HTML
<img id="img" />
<button onclick="downloadFile()">Get Resource</button>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.rawgit.com/rikmms/progress-bar-4-axios/0a3acf92/dist/index.js"></script>
// add the following to customize the style
<style>
#nprogress .bar {
background: red !important;
}
#nprogress .peg {
box-shadow: 0 0 10px red, 0 0 5px red !important;
}
#nprogress .spinner-icon {
border-top-color: red !important;
border-left-color: red !important;
}
</style>然后像這樣實(shí)現(xiàn)進(jìn)度條:
<script type="text/javascript">
loadProgressBar();
function downloadFile() {
getRequest(
"https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg"
);
}
function getRequest(url) {
axios
.get(url, { responseType: "blob" })
.then(function (response) {
const reader = new window.FileReader();
reader.readAsDataURL(response.data);
reader.onload = () => {
document.getElementById("img").setAttribute("src", reader.result);
};
})
.catch(function (error) {
console.log(error);
});
}
</script>代碼使用FileReaderAPI異步讀取下載的圖像。
readAsDataURL方法以Base64編碼字符串的形式返回圖像的數(shù)據(jù),然后將其插入到img標(biāo)記的src屬性中以顯示圖像。
并發(fā)請(qǐng)求
為了同時(shí)發(fā)出多個(gè)請(qǐng)求,Axios提供axios.all()方法。
只需將請(qǐng)求數(shù)組傳遞給此方法,然后使用axios.spread()將響應(yīng)數(shù)組的屬性分配給單獨(dú)的變量:
axios.all([
axios.get('https://api.github.com/users/iliakan'),
axios.get('https://api.github.com/users/taylorotwell')
])
.then(axios.spread((obj1, obj2) => {
// Both requests are now complete
console.log(obj1.data.login + ' has ' + obj1.data.public_repos + ' public repos on GitHub');
console.log(obj2.data.login + ' has ' + obj2.data.public_repos + ' public repos on GitHub');
}));也可以使用內(nèi)置的Promise.all()方法獲得相同的結(jié)果。
將所有fetch請(qǐng)求作為數(shù)組傳遞給Promise.all()。接著使用async函數(shù)處理響應(yīng),如下所示:
Promise.all([
fetch('https://api.github.com/users/iliakan'),
fetch('https://api.github.com/users/taylorotwell')
])
.then(async([res1, res2]) => {
const a = await res1.json();
const b = await res2.json();
console.log(a.login + ' has ' + a.public_repos + ' public repos on GitHub');
console.log(b.login + ' has ' + b.public_repos + ' public repos on GitHub');
})
.catch(error => {
console.log(error);
});結(jié)論
Axios在緊湊的軟件包中提供了一個(gè)易于使用的API,可滿足大多數(shù)HTTP通信需求。
而web瀏覽器提供的fetch()方法則能完全重現(xiàn)Axios庫的主要功能。
所以,是否加載客戶端HTTP API取決于你是否習(xí)慣使用內(nèi)置API。
編程快樂!

























