大家好,很高興又見面了,我是"高級前端進階",由我帶著大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點讚、收藏、轉發!
本文我會解釋為什麼在開發中 Axios 是比 Fetch() 更好的選擇。
概述和語法
Fetch
Fetch() 是 Fetch API 中 JavaScript window 對象的一個方法。它是內置的,所以開發者無需進行任何安裝。Fetch() 允許我們在不安裝其他庫的情況下異步請求數據。
fetch(url)
.then((res) => {
// 處理響應
})
.catch((error) => {
// 處理錯誤
});
上面的代碼是一個簡單的基於 fetch() 的 get 請求。fetch() 默認可以接收一個 url 參數,也即用戶請求獲取數據的路徑。之後,fecth() 返回一個 promise,它可能會 resolve 響應對象,也可能會 reject 一個錯誤。
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/JSON",
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(response => console.log(response))
.catch(error => console.log(error));
第二個參數是用於配置的選項,它是可選的。如果用戶不傳遞第二個參數,則默認總是進行 get 請求,並從給定 url 那裡下載相關資源。正如我之前提及的,promise 進行 resolve 的只是一個響應對象(譯者註:第一個 response,並且是一個流對象),因此開發者還需要使用其他方法異步獲取實際的響應數據。響應數據類型不同,對應的方法也不同。
- response.json()
- response.text()
- response.blob()
- response.formData()
- response.arrayBuffer()
最常用的是 response.json()。
不幸的是,Node.js 並沒有內置的 fetch() 函數,不過我們可以使用諸如 node-fetch 這樣的 polyfill。在瀏覽器版本的 fetch() 和 node-fetch 之間,還存在著不少變種。
Axios
Axios 是一個可以從 Node 端或者瀏覽器端發起 HTTP 請求的 JavaScript 請求庫。作為一個現代化的庫,它基於 Promise API。Axios 有著諸多優點,比如防禦 CSRF 攻擊等。要使用 Axios,開發者必須先進行安裝並通過 CDN、npm、yarn 或者 Bower 導入到項目中。
axios.get(url)
.then((response) => console.log(response))
.catch((error) => console.log(error));
上面這段代碼使用了 Axios 的 get 方法,並針對響應和錯誤設置了相應的回調函數。當開發者創建一個配置對象的時候,他們可以定義一系列的屬性。最常用的有 url、baseURL、 params、 auth、headers、responseType 以及 data。
作為響應,Axios 會返回一個 promise,該 promise 會 resolve 響應對象,或者 reject 錯誤對象。響應對象可能有以下屬性:
- data: 實際的響應體
- status: 返迴響應的 HTTP 狀態碼,比如 200 或者 404
- statusText: 文本信息形式的 HTTP 狀態
- headers: 響應頭,和請求頭類似
- config: 請求的配置
- request: XMLHttpRequest (XHR) 對象
axios({
url: "http://api.com",
method: "POST",
header: {
"Content-Type": "application/json",
},
data: {
name: "Sabesan",
age: 25
}
});
使用 fecth() 必須處理兩個 promise,而使用 Axios 則只需要處理一個,並且可以編寫更加簡潔的代碼。
Axios 使用 data 屬性處理數據,而 fetch() 則使用 body 屬性。fetch() 的 data 是經過序列化的,並且 URL 作為參數傳遞,而 Axios 的 URL 則是直接在配置對象中設置。
JSON
Fetch
使用 fetch() 的時候,開發者需要在請求攜帶數據的時候去序列化數據,並且對於響應回來的數據需要調用某些方法進行反序列化。
fetch('url')
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.log(error));
在上面這段代碼中,開發者拿到響應數據後還需要調用 response.json() 進行處理。也就是說,這裡有兩個步驟,一個是發出實際的請求,一個是調用 .json() 處理響應數據。
Axios
使用 Axios,開發者直接在請求中攜帶數據或者從響應中獲取數據,無需進行額外處理。
axios.get('url')
.then((response)=>console.log(response))
.catch((error)=>console.log(error))
上面的例子中,我們只用到了一個 then。
數據自動轉化是 Axios 非常棒的一個特性。
錯誤處理
Fetch
每次調用 fetch() 方法返迴響應之後,你都需要手動檢查狀態碼是否成功,因為即使是 400 或者 500 這樣的狀態碼,也會被認為是請求成功。在使用 fetch() 的情況下,只有未完成的請求才不會被 resolve。
fetch('url')
.then((response)=>{
if(!response.ok){
throw Error (response.statusText);
}
return response.json();
})
.then((data)=>console.log(data))
.catch((error)=>console.log(error))
Fetch() 對於失敗的狀態碼不會拋出錯誤,因此你必須手動檢查 response.ok 屬性。你可以將錯誤檢查封裝成一個函數以提升它的可重用性:
const checkError = response => {
if (!response.ok) throw Error(response.statusText);
return response.json();
};
fetch("url")
.then(checkError)
.then(data => console.log(data))
.catch(error => console.log("error", error));
Axios
但如果使用 Axios ,錯誤處理將會變得非常簡單,因為 Axios 可以準確地捕獲並拋出錯誤。比如說返回 404 響應的時候,promise 會處於 reject 狀態,並返回一個錯誤。這時候,我們可以捕獲這個錯誤,並且檢查具體是什麼錯誤類型。
axios.get('url')
.then((response)=> console.log(response))
.catch((error)=>{
if(error.response){
// 當狀態碼不是 2xx 類型的時候
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.Request){
// 當請求發出後沒有收到響應的時候
console.log(error.request);
} else {
// 其它錯誤
console.log(error.message);
}
})
下載進度
當加載大型資源的時候,進度指示器對低網速用戶來說非常有用。在以前,開發者是通過 XMLHttpRequest.onprogress 來跟蹤下載進度的。
Fetch
要在 fetch() 中跟蹤下載進度,我們可以使用 response.body 的相關屬性。這是一個 ReadableStream 對象,它可以逐塊地提供 body,並且隨時告訴我們下載了多少數據。
// 原始碼: https://github.com/AnthumChris/fetch-progress-indicators
const element = document.getElementById('progress');
fetch('url')
.then(response => {
if (!response.ok) {
throw Error(response.status+' '+response.statusText)
}
// 確保瀏覽器支持 ReadableStream
if (!response.body) {
throw Error('ReadableStream not yet supported in this browser.')
}
// 以字節存儲實體大小
const contentLength = response.headers.get('content-length');
// 確保瀏覽器支持 contentLength
if (!contentLength) {
throw Error('Content-Length response header unavailable');
}
// 將整型解析為一個 base-10 數字
const total = parseInt(contentLength, 10);
let loaded = 0;
return new Response(
// 創建並返回一個可讀流
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 =>
// 基於數據構造一個 blob
response.blob()
)
.then(data => {
// 將已下載圖片插入到頁面中
document.getElementById('img').src = URL.createObjectURL(data);
})
.catch(error => {
console.error(error);
})
function progress({loaded, total}) {
element.innerHTML = Math.round(loaded/total*100)+'%';
}
上面的代碼展示了在下載圖片的時候,如何使用 ReadableStream 為用戶提供即時反饋。
Axios
在 Axios 中也可以實現進度指示器,並且更加簡單,因為你只需要安裝並使用相應模塊即可,這個模塊就是 Axios Progress Bar。
loadProgressBar();
function downloadFile(url) {
axios.get(url, {responseType: 'blob'})
.then(response => {
const reader = new window.FileReader();
reader.readAsDataURL(response.data);
reader.onload = () => {
document.getElementById('img').setAttribute('src', reader.result);
}
})
.catch(error => {
console.log(error)
});
}
上傳進度
Fetch
在 fetch() 中無法跟蹤上傳進度。
Axios
在 Axios 中可以跟蹤上傳進度。如果你正在開發一個視頻 / 圖片上傳應用,那麼使用 Axios 是個不錯的選擇。
const config = {
onUploadProgress: event => console.log(event.loaded)
};
axios.put("/api", data, config);
HTTP 攔截器
當你需要檢查或者修改從客戶端到服務端的 HTTP 請求時,攔截器就可以派上用場了。當然它也可以用在其它地方,比如身份驗證,日誌記錄等。
Fetch
Fetch() 默認並沒有提供 HTTP 攔截器的功能。雖說你可以重寫 fetch() 方法並定義在請求發出時的行為,但比起直接使用 Axios 的功能,這樣需要編寫大量複雜的代碼。如果你想要重寫全局 fetch() 方法並定義自己的攔截器,可以參考下面的代碼:
fetch = (originalFetch => {
return (...arguments) => {
const result = originalFetch.apply(this, arguments);
return result.then(console.log('Request was sent'));
};
})(fetch);
fetch('url')
.then(response => response.json())
.then(data => {
console.log(data)
});
Axios
Axios 的 HTTP 攔截器是它的核心特性之一 —— 也就是說你無需編寫額外代碼去實現攔截器。
// request 攔截器
axios.interceptors.request.use((config)=>{
console.log('Request was sent');
return config;
})
// response 攔截器
axios.interceptors.response.use((response) => {
return response;
})
axios.get('url')
.then((response)=>console.log(response))
.catch((error)=>console.log(error))
在上面這段代碼中, axios.interceptors.request.use() 和 axios.interceptors.response.use() 方法分別定義在 HTTP 請求發出前和響應收到前需要進行的操作。
響應超時處理
Fetch
Fetch() 可以通過 AbortController 接口處理響應超時問題。
const controller = new AbortController();
const signal = controller.signal;
const options = {
method: 'POST',
signal: signal,
body: JSON.stringify({
firstName: 'Sabesan',
lastName: 'Sathananthan'
})
};
const promise = fetch('/login', options);
const timeoutId = setTimeout(() => controller.abort(), 5000);
promise
.then(response => {/* handle the response */})
.catch(error => console.error('timeout exceeded'));
在上面這段代碼中,我們通過 AbortController.AbortController() 構造器創建一個 AbortController 對象。這個對象允許我們延遲終止請求。正如我之前在別的文章提到的, AbortController 有一個 signal 可讀屬性,它可用於與一個請求進行交互或者終止請求。如果服務端沒有在 5 秒內進行響應,那麼就會通過 controller.abort() 終止請求。
Axios
通過使用配置對象中可選的 timeout 屬性,你可以設置一個終止請求的超時時間。
axios({
method: 'post',
url: '/login',
timeout: 5000, // 5 秒超時時間
data: {
firstName: 'Sabesan',
lastName: 'Sathananthan'
}
})
.then(response => {/* handle the response */})
.catch(error => console.error('timeout exceeded'))
JavaScript 開發者更喜歡 Axios 而不是 fetch() 的其中一個原因就是前者處理超時問題更加簡單。
並發請求
Fetch
你可以使用內置的 Promise.all() 實現並發請求。只需要傳遞一個包含多個 fetch() 請求的數組作為參數即可,之後通過一個 async 函數處理響應數據。
Promise.all([
fetch('https://api.github.com/users/sabesansathananthan'),
fetch('https://api.github.com/users/rcvaram')
])
.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);
});
Axios
在 Axios 中,我們可以使用 axios.all()。將所有請求放在一個數據中並傳遞給 axios.all() 方法,之後調用 axios.spread() 方法將響應數組中的每個響應數據賦值給單個變量,就像這樣:
axios.all([
axios.get('https://api.github.com/users/sabesansathananthan'),
axios.get('https://api.github.com/users/rcvaram')
])
.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');
}));
向後兼容
向後兼容可以說就是瀏覽器的支持。
Fetch
Fetch() 只支持 Chrome 42+、Safari 10.1+、Firefox 39+ 和 Edge 14+。具體的兼容情況可以查看 Can I Use。為了在不支持 fetch() 的瀏覽器上實現類似它的功能,你可以使用諸如 windows.fetch ( )這樣的 polyfill。
通過 npm 進行安裝:
npm install whatwg-fetch --save
如果由於某些原因你需要訪問 polyfill 的實現,也可以進行導出:
import {fetch as fetchPolyfill} from 'whatwg-fetch'
window.fetch(...) // 原生瀏覽器版本
fetchPolyfill(...) // polyfill 版本
記住,在舊瀏覽器中你可能還需要 promise 的 polyfill。
Axios
Axios 和 fetch() 不一樣。它提供了相當全面的瀏覽器支持,即使像 IE11 這樣的舊瀏覽器也可以順利運行 Axios。更具體的兼容情況可以查看 Axios 的 文檔。
結論
對於大部分進行 HTTP 交互的開發場景,Axios 提供了一系列簡單易用的 API。
當然還有不少用於 HTTP 交互的第三方庫,比如基於 window.fetch 的 ky ,這是一個小而優雅的 HTTP 請求庫;比如說基於 XMLHttpRequest 的 superagent,它是一個小型的、漸進式的客戶端 HTTP 請求庫。
但如果應用需要進行大量請求,或者要求請求庫可以高效進行錯誤處理和 HTTP 攔截,那麼 Axios 是更好的選擇。
而對於只需要調用少量 API 的小項目來說,fetch() 也是個不錯的選擇。
參考資料
- 原文連結:https://betterprogramming.pub/why-javascript-developers-should-prefer-axios-over-fetch-294b28a96e2c
- 作者:Sabesan Sathananthan
- 譯文連結:https://markdowner.net/article/177804698764861440