axios 是 Vue 官方推薦的一個 HTTP 庫,用 axios 官方簡介來介紹它,就是:vue
Axios 是一個基於 promise 的 HTTP 庫,能夠用在瀏覽器和 node.js 中。java
做爲一個優秀的 HTTP 庫,axios 戰勝了曾經由 Vue 官方團隊維護的 vue-resource,得到了 Vue 做者尤小右的大力推薦,成爲了 Vue 項目中 HTTP 庫的最佳選擇。node
雖然,axios 是個優秀的 HTTP 庫,可是,直接在項目中使用並非那麼方便,因此,咱們須要對其進行必定程度上的配置封裝,減小重複代碼,方便調用。下面,咱們就來聊聊 Vue 中 axios 的封裝。ios
其實,網上關於 axios 封裝的代碼很多,可是大部分都是在入口文件(main.js)中進行 axios 全局對象屬性定義的形式進行配置,相似於以下代碼:typescript
axios.defaults.timeout = 10000
複製代碼
該方案有兩個不足,首先,axios 封裝代碼耦合進入入口文件,不方便後期維護;其次,使用 axios 全局對象屬性定義的方式進行配置,代碼過於零散。json
針對問題一,我使用了 Vue 源碼結構中的一大核心思想——將功能拆分爲文件,方便後期的維護。單首創建一個 http.js
或者 http.ts
文件,在文件中引入 axios 並對其進行封裝配置,最後將其導出並掛載到 Vue 的原型上便可。此時,每次修改 axios 配置,只須要修改對應的文件便可,不會影響到不相關的功能。axios
針對問題二,採用 axios 官方推薦的,經過配置項建立 axios 實例的方式進行配置封裝。api
代碼以下:跨域
// http.js
import axios from 'axios'
// 建立 axios 實例
const service = axios.create({
// 配置項
})
複製代碼
baseURL 屬性是請求地址前綴,將自動加在 url 前面,除非 url 是個絕對地址。正常狀況下,在開發環境下和生產模式下有着不一樣的 baseURL,因此,咱們須要根據不一樣的環境切換不一樣的 baseURL。promise
在開發模式下,因爲有着 devServer 的存在,須要根據固定的 url 前綴進行請求地址重寫,因此,在開發環境下,將 baseURL 設爲某個固定的值,好比:/apis
。
在生產模式下,根據 Java 模塊的請求前綴的不一樣,能夠設置不一樣的 baseURL。
具體代碼以下:
// 根據 process.env.NODE_ENV 區分狀態,切換不一樣的 baseURL
const service = axios.create({
baseURL: process.env.NODE_ENV === 'production' ? `/java` : '/apis',
})
複製代碼
在這裏和你們聊一個問題,什麼是封裝?在我看來,封裝是經過更少的調用代碼覆蓋更多的調用場景。
因爲,大部分狀況下,請求頭都是固定的,只有少部分狀況下,會須要一些特殊的請求頭,因此,在這裏,我採用的方案是,將普適性的請求頭做爲基礎配置。當須要特殊請求頭時,將特殊請求頭做爲參數傳入,覆蓋基礎配置。
代碼以下:
const service = axios.create({
...
headers: {
get: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
// 在開發中,通常還須要單點登陸或者其餘功能的通用請求頭,能夠一併配置進來
},
post: {
'Content-Type': 'application/json;charset=utf-8'
// 在開發中,通常還須要單點登陸或者其餘功能的通用請求頭,能夠一併配置進來
}
},
})
複製代碼
axios 中,提供是否容許跨域的屬性——withCredentials,以及配置超時時間的屬性——timeout,經過這兩個屬性,能夠輕鬆處理跨域和超時的問題。
下面,咱們來講說響應碼處理:
axios 提供了 validateStatus 屬性,用於定義對於給定的HTTP 響應狀態碼是 resolve 或 reject promise。因此,正常設置的狀況下,咱們會將狀態碼爲 2 系列或者 304 的請求設爲 resolve 狀態,其他爲 reject 狀態。結果就是,咱們能夠在業務代碼裏,使用 catch 統一捕獲響應錯誤的請求,從而進行統一處理。
可是,因爲我在代碼裏面使用了 async-await,而衆所周知,async-await 捕獲 catch 的方式極爲麻煩,因此,在此處,我選擇將全部響應都設爲 resolve 狀態,統一在 then 處理。
此部分代碼以下:
const service = axios.create({
// 跨域請求時是否須要使用憑證
withCredentials: true,
// 請求 30s 超時
timeout: 30000,
validateStatus: function () {
// 使用async-await,處理reject狀況較爲繁瑣,因此所有返回resolve,在業務代碼中處理異常
return true
},
})
複製代碼
在不使用 axios 的狀況下,每次請求或者接受響應,都須要將請求或者響應序列化。
而在 axios 中, transformRequest
容許在向服務器發送請求前,修改請求數據;transformResponse
在傳遞給 then/catch 前,容許修改響應數據。
經過這兩個鉤子,能夠省去大量重複的序列化代碼。
代碼以下:
const service = axios.create({
// 在向服務器發送請求前,序列化請求數據
transformRequest: [function (data) {
data = JSON.stringify(data)
return data
}],
// 在傳遞給 then/catch 前,修改響應數據
transformResponse: [function (data) {
if (typeof data === 'string' && data.startsWith('{')) {
data = JSON.parse(data)
}
return data
}]
})
複製代碼
攔截器,分爲請求攔截器以及響應攔截器,分別在請求或響應被 then 或 catch 處理前攔截它們。
以前提到過,因爲 async-await 中 catch 難以處理的問題,因此將出錯的狀況也做爲 resolve 狀態進行處理。但這帶來了一個問題,請求或響應出錯的狀況下,結果沒有數據協議中定義的 msg 字段(消息)。因此,咱們須要在出錯的時候,手動生成一個符合返回格式的返回數據。
因爲,在業務中,沒有須要在請求攔截器中作額外處理的需求,因此,請求攔截器的 resolve 狀態,只需直接返回就能夠了。
請求攔截器代碼以下:
// 請求攔截器
service.interceptors.request.use((config) => {
return config
}, (error) => {
// 錯誤拋到業務代碼
error.data = {}
error.data.msg = '服務器異常,請聯繫管理員!'
return Promise.resolve(error)
})
複製代碼
再來聊聊響應攔截器,仍是以前的那個問題,除了請求或響應錯誤,還有一種狀況也會致使返回的消息體不符合協議規範,那就是狀態碼不爲 2 系列或 304 時。此時,咱們仍是須要作同樣的處理——手動生成一個符合返回格式的返回數據。可是,有一點不同,咱們還須要根據不一樣的狀態碼生成不一樣的提示信息,以方便處理上線後的問題。
響應攔截器代碼以下:
// 根據不一樣的狀態碼,生成不一樣的提示信息
const showStatus = (status) => {
let message = ''
// 這一坨代碼可使用策略模式進行優化
switch (status) {
case 400:
message = '請求錯誤(400)'
break
case 401:
message = '未受權,請從新登陸(401)'
break
case 403:
message = '拒絕訪問(403)'
break
case 404:
message = '請求出錯(404)'
break
case 408:
message = '請求超時(408)'
break
case 500:
message = '服務器錯誤(500)'
break
case 501:
message = '服務未實現(501)'
break
case 502:
message = '網絡錯誤(502)'
break
case 503:
message = '服務不可用(503)'
break
case 504:
message = '網絡超時(504)'
break
case 505:
message = 'HTTP版本不受支持(505)'
break
default:
message = `鏈接出錯(${status})!`
}
return `${message},請檢查網絡或聯繫管理員!`
}
// 響應攔截器
service.interceptors.response.use((response) => {
const status = response.status
let msg = ''
if (status < 200 || status >= 300) {
// 處理http錯誤,拋到業務代碼
msg = showStatus(status)
if (typeof response.data === 'string') {
response.data = { msg }
} else {
response.data.msg = msg
}
}
return response
}, (error) => {
// 錯誤拋到業務代碼
error.data = {}
error.data.msg = '請求超時或服務器異常,請檢查網絡或聯繫管理員!'
return Promise.resolve(error)
})
複製代碼
tips1:友情提示,上面那一坨 switch-case 代碼,可使用策略模式進行優化~
tips2:若是有一些業務相關的需求,能夠加在攔截器中,好比:loading、鑑權等~
因爲前段時間,我在部門內推了 TypeScript,爲了知足本身的強迫症,將全部 js 文件改寫爲了 ts 文件。因爲 axios 自己有 TypeScript 相關的支持,因此只須要把對應的類型導入,而後賦值便可。
// http.ts
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
const showStatus = (status: number) => {
let message = ''
switch (status) {
case 400:
message = '請求錯誤(400)'
break
case 401:
message = '未受權,請從新登陸(401)'
break
case 403:
message = '拒絕訪問(403)'
break
case 404:
message = '請求出錯(404)'
break
case 408:
message = '請求超時(408)'
break
case 500:
message = '服務器錯誤(500)'
break
case 501:
message = '服務未實現(501)'
break
case 502:
message = '網絡錯誤(502)'
break
case 503:
message = '服務不可用(503)'
break
case 504:
message = '網絡超時(504)'
break
case 505:
message = 'HTTP版本不受支持(505)'
break
default:
message = `鏈接出錯(${status})!`
}
return `${message},請檢查網絡或聯繫管理員!`
}
const service = axios.create({
// 聯調
baseURL: process.env.NODE_ENV === 'production' ? `/` : '/apis',
headers: {
get: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
},
post: {
'Content-Type': 'application/json;charset=utf-8'
}
},
// 是否跨站點訪問控制請求
withCredentials: true,
timeout: 30000,
transformRequest: [(data) => {
data = JSON.stringify(data)
return data
}],
validateStatus () {
// 使用async-await,處理reject狀況較爲繁瑣,因此所有返回resolve,在業務代碼中處理異常
return true
},
transformResponse: [(data) => {
if (typeof data === 'string' && data.startsWith('{')) {
data = JSON.parse(data)
}
return data
}]
})
// 請求攔截器
service.interceptors.request.use((config: AxiosRequestConfig) => {
return config
}, (error) => {
// 錯誤拋到業務代碼
error.data = {}
error.data.msg = '服務器異常,請聯繫管理員!'
return Promise.resolve(error)
})
// 響應攔截器
service.interceptors.response.use((response: AxiosResponse) => {
const status = response.status
let msg = ''
if (status < 200 || status >= 300) {
// 處理http錯誤,拋到業務代碼
msg = showStatus(status)
if (typeof response.data === 'string') {
response.data = {msg}
} else {
response.data.msg = msg
}
}
return response
}, (error) => {
// 錯誤拋到業務代碼
error.data = {}
error.data.msg = '請求超時或服務器異常,請檢查網絡或聯繫管理員!'
return Promise.resolve(error)
})
export default service
複製代碼