Axios vs. fetch():哪個最適合 HTTP 請求?
因為Axios的易于使用,所以有些開發(fā)人員比起內(nèi)置的API,更喜歡Axios。
但許多人高估了這個庫。
fetch() API不但完全能夠重現(xiàn)Axios的關(guān)鍵功能,而且還有隨時可用于所有現(xiàn)代瀏覽器中的獨(dú)特優(yōu)勢。
在本文中,我將按照基本語法、向后兼容性、響應(yīng)超時、自動JSON數(shù)據(jù)轉(zhuǎn)換、HTTP攔截器、下載進(jìn)度、同時請求這些方面來比較fetch()和Axios,看看它們?nèi)绾螆?zhí)行任務(wù)。
希望在本文結(jié)束時,大家對這兩個API有了更深入的了解。
基本語法
在我們深入研究Axios更高級地功能之前,先與fetch()進(jìn)行基本語法的比較。
下面是Axios如何將帶有自定義請求頭的[POST]請求發(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自動轉(zhuǎn)換從服務(wù)器返回的數(shù)據(jù),但使用fetch()時,你必須調(diào)用response.json方法將數(shù)據(jù)解析為JavaScript對象。
- 使用Axios,服務(wù)器提供的數(shù)據(jù)響應(yīng)可以在數(shù)據(jù)對象中訪問,而對于fetch()方法,最終數(shù)據(jù)可以命名為任何變量
向后兼容性
Axios的主要賣點(diǎn)之一是其廣泛的瀏覽器支持。
即使是像IE11這樣的舊瀏覽器也可以毫無問題地運(yùn)行Axios。這是因為它背后使用了XMLHttpRequest。
而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
然后,提出如下請求:
import 'whatwg-fetch'
window.fetch(...)
謹(jǐn)記,在有些舊瀏覽器中,可能還需要promise polyfill。
響應(yīng)超時
在Axios中設(shè)置超時的簡單性,是一些開發(fā)人員比fetch()更喜歡Axios的原因之一。
在Axios中,你可以使用配置對象的timeout屬性來設(shè)置請求中止之前的毫秒數(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對象,它允許我們稍后中止請求。
Signal是AbortController的只讀屬性,提供了一種與請求通信或中止請求的方法。
如果服務(wù)器在4秒內(nèi)沒有響應(yīng),則調(diào)用controller.abort(),終止操作。
自動JSON數(shù)據(jù)轉(zhuǎn)換
如前所述,Axios在發(fā)送請求時會自動字符串化數(shù)據(jù)(當(dāng)然你也可以覆蓋默認(rèn)行為并定義不同的轉(zhuǎn)換機(jī)制)。
但是,當(dāng)使用fetch()時,你必須手動執(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));
自動轉(zhuǎn)換數(shù)據(jù)是一個不錯的功能,但同樣,這不是你不能用fetch()做的事情。
HTTP攔截器
Axios的主要功能之一是它能夠攔截HTTP請求。
當(dāng)你需要檢查或更改從應(yīng)用程序到服務(wù)器的HTTP請求時,使用HTTP攔截器非常方便,從服務(wù)器到應(yīng)用程序亦是如此(例如,日志記錄、身份驗證或重試失敗的HTTP請求)。
使用攔截器就不必為每個HTTP請求編寫單獨(dú)的代碼。
在你想要為處理請求和響應(yīng)設(shè)置全局策略時,HTTP攔截器非常有用。
以下是在Axios中聲明請求攔截器的方法:
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請求之前要運(yùn)行的代碼。而axios.interceptors.response.use()用于攔截來自服務(wù)器的響應(yīng)。
假設(shè)存在網(wǎng)絡(luò)錯誤,那么通過響應(yīng)偵聽器,可以重試相同的請求。
默認(rèn)情況下,fetch()不提供攔截請求的方法,但它的解決方法也并不復(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)度條在加載時非常有用,尤其是對于互聯(lián)網(wǎng)速度較慢的用戶。
以前,JavaScript程序員使用XMLHttpRequest.onprogress回調(diào)處理程序來實(shí)現(xiàn)進(jìn)度指示器。
Fetch API沒有onprogress處理程序。事實(shí)上,它通過響應(yīng)對象的body屬性來提供ReadableStream的實(shí)例。
以下示例表明如何使用ReadableStream在圖像下載期間為用戶提供即時反饋:
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)度條模塊時。
首先,包含以下樣式和腳本:
// 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ā)請求
為了同時發(fā)出多個請求,Axios提供axios.all()方法。
只需將請求數(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請求作為數(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在緊湊的軟件包中提供了一個易于使用的API,可滿足大多數(shù)HTTP通信需求。
而web瀏覽器提供的fetch()方法則能完全重現(xiàn)Axios庫的主要功能。
所以,是否加載客戶端HTTP API取決于你是否習(xí)慣使用內(nèi)置API。
編程快樂!