項目實戰(zhàn)之接口處理篇~一文搞定接口請求
在項目開發(fā)中,接口請求是必不可少的,為了方便使用和維護,大家都會將接口請求的方法二次封裝。下面小編將我項目中接口封裝使用的方法分享給大家,希望可以幫到大家。喜歡的給個三連擊再走喲。
目前前端常用的請求方式主要有兩種:axios、Fetch。下面小編就這兩種給大家詳細的介紹介紹。
axios
axios 是一個基于Promise 用于瀏覽器和 nodejs 的 HTTP 客戶端,本質(zhì)上也是對原生XHR的封裝,只不過它是Promise的實現(xiàn)版本,符合最新的ES規(guī)范。
特點
從瀏覽器中創(chuàng)建 XMLHttpRequest
支持 Promise API
客戶端支持防止CSRF
提供了一些并發(fā)請求的接口(重要,方便了很多的操作)
從 node.js 創(chuàng)建 http 請求
攔截請求和響應(yīng)
轉(zhuǎn)換請求和響應(yīng)數(shù)據(jù)
取消請求
自動轉(zhuǎn)換JSON數(shù)據(jù)
兼容性問題
axios在PC端瀏覽器的兼容性問題
axios支持IE8+,但原理是基于promise之上實現(xiàn)的,因此會存在不兼容IE的問題。
trident內(nèi)核的瀏覽器下會報:vuex requires a Promise polyfill in this browser
解決方式:
安裝 babel-polyfill
npm install babel-polyfill -s
安裝成功以后需要在 main.js 中引入 babel-polyfill
一般會配置 webpack.base.config.js 中 entry
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: ["babel-polyfill", "./src/main.js"]
// app: './src/main.js'
},
}
axios在安卓低版本兼容性處理
在較低版本的安卓手機中發(fā)現(xiàn)發(fā)現(xiàn)封裝的axios請求無效,主要原因還是低版本的安卓手機無法使用promise
解決方式:
安裝 es6-promise
npm install es6-promise -s
引入注冊es6-promise
一定要在axios注冊之前
// 注意: es6-promise 一定要在 axios 之前注冊
promise.polyfill()
或者
require('es6-promise').polyfill();
二次封裝axios
設(shè)置baseUrl
axios.defaults.baseURL = baseUrl
設(shè)置超時時間
axios.defaults.timeout = 10000
請求攔截
axios提供了請求攔截,在這里我們可以做一些請求前的統(tǒng)一處理,比如token等。
// 請求攔截
axios.interceptors.request.use((config) => {
NProgress.start()
let token = sessionStorage.token
if (token) {
config.headers.x_access_token = token
}
return config
}, function (error) {
return Promise.reject(error)
})
響應(yīng)攔截
axios提供了響應(yīng)攔截,在這里我們可以做一些響應(yīng)后的數(shù)據(jù)進行統(tǒng)一處理,比如報錯提示等。
const errorCode = {
'000': '操作太頻繁,請勿重復(fù)請求',
'401': '當(dāng)前操作沒有權(quán)限',
'403': '當(dāng)前操作沒有權(quán)限',
'404': '資源不存在',
'417': '未綁定登錄賬號,請使用密碼登錄后綁定',
'423': '演示環(huán)境不能操作,如需了解聯(lián)系冷冷',
'426': '用戶名不存在或密碼錯誤',
'428': '驗證碼錯誤,請重新輸入',
'429': '請求過頻繁',
'479': '演示環(huán)境,沒有權(quán)限操作',
'default': '系統(tǒng)未知錯誤,請反饋給管理員',
'40006':'登錄失效,請重新登錄'
}
// 響應(yīng)攔截
axios.interceptors.response.use(function (response) {
const status = Number(response.data.code) || 200
const msg = response.data.message || errorCode[status] || errorCode['default']
if (response.data.code === 40006) {//token失效
//......
return Promise.reject(msg)
}
if (response.status !== 200 || response.data.code !== 200) {//接口報錯,提示錯誤信息
message.error(msg);
return Promise.reject(msg)
}
return response
}, function (error) {
if (axios.isCancel(error)) {
requestList.length = 0
throw new axios.Cancel('cancel request')
} else {
message.error('網(wǎng)絡(luò)請求失敗,請重試')
}
return Promise.reject(error)
})
全局進度條
在接口請求的時候,為了優(yōu)化用戶體驗,我們可以給接口增加一個全局的進度條。我習(xí)慣使用的是nprogress。
安裝
npm i nprogress -S
使用
nprogress提供了兩個api:start()、done();分別是開始、結(jié)束,我們分別將這兩個方法加入到請求攔截器和響應(yīng)攔截器之中就可以了。
import NProgress from 'nprogress' // 引入nprogress插件
import 'nprogress/nprogress.css' // 這個nprogress樣式必須引入
// 請求攔截
axios.interceptors.request.use((config) => {
NProgress.start()
let token = sessionStorage.token
if (token) {
config.headers.x_access_token = token
}
return config
}, function (error) {
return Promise.reject(error)
})
// 響應(yīng)攔截
axios.interceptors.response.use(function (response) {
NProgress.start()
const status = Number(response.data.code) || 200
const msg = response.data.message || errorCode[status] || errorCode['default']
if (response.data.code === 40006) {//token失效
//......
return Promise.reject(msg)
}
if (response.status !== 200 || response.data.code !== 200) {//接口報錯,提示錯誤信息
message.error(msg);
return Promise.reject(msg)
}
return response
}, function (error) {
if (axios.isCancel(error)) {
requestList.length = 0
throw new axios.Cancel('cancel request')
} else {
message.error('網(wǎng)絡(luò)請求失敗,請重試')
}
return Promise.reject(error)
})
使用封裝
為了方便使用,我們可以對axios的使用方法進行封裝,這樣便于在項目中使用,封裝方法如下:
const request = function ({ url, params, config, method }) {
// 如果是get請求 需要拼接參數(shù)
let str = ''
if (method === 'get' && params) {
Object.keys(params).forEach(item => {
str += `${item}=${params[item]}&`
})
}
return new Promise((resolve, reject) => {
axios[method](str ? (url + '?' + str.substring(0, str.length - 1)) :
url, params, Object.assign({}, config)).then(response => {
resolve(response.data)
}, err => {
if (err.Cancel) {
} else {
reject(err)
}
}).catch(err => {
reject(err)
})
})
}
export default request
這樣我們使用的時候,只需要傳對應(yīng)的參數(shù)就可以了。
使用示例
import request from '../../utils/axios'
import {api} from '../../utils/env'
export function login (params) {
return request({
url: api+'user/login',
method: 'post',
params: params
})
}
Fetch
特點
語法簡潔,更加語義化
基于標準 Promise 實現(xiàn),支持 async/await
脫離了XHR,是ES規(guī)范里新的實現(xiàn)方式
更加底層,提供的API豐富(request, response)
使用isomorphic-fetch可以方便同步
劣勢
fetch是一個低層次的API,你可以把它考慮成原生的XHR,所以使用起來并不是那么舒服,需要進行封裝
fetch只對網(wǎng)絡(luò)請求報錯,對400,500都當(dāng)做成功的請求,服務(wù)器返回 400,500 錯誤碼時并不會 reject,只有網(wǎng)絡(luò)錯誤這些導(dǎo)致請求不能完成時,fetch 才會被 reject
fetch默認不會帶cookie,需要添加配置項: fetch(url, {credentials: ‘include’})
fetch不支持abort,不支持超時控制,使用setTimeout及Promise.reject的實現(xiàn)的超時控制并不能阻止請求過程繼續(xù)在后臺運行,造成了流量的浪費
fetch沒有辦法原生監(jiān)測請求的進度,而XHR可以
兼容性問題
fetch是相對比較新的技術(shù),因此也就會存在一些瀏覽器兼容問題,接下來我們借can i use的一張圖說明fetch在各大瀏覽器的兼容性。
兼容低版本瀏覽器
fetch不兼容低版本瀏覽器是因為低版本瀏覽器不支持Proimse導(dǎo)致的。
解決方案: 手動寫個文件引入pinkie-promise,判斷當(dāng)前是否有window.Promise,如果沒有就把我引入的作為window.Promise.
步驟:
安裝whatwg-fetch
npm install whatwg-fetch -save
由于 whatwg-fetch模塊沒有pinkie-promise,因此需要在模塊內(nèi)引入
cd /node_modules/whatwg-fetch
npm install pinkie-fetch --save
引入模塊
在fetch請求文件中引入模塊
import 'whatwg-fetch'
二次封裝
下面小編提供一個小編自己的封裝,是基于ts的fetch二次封裝。
定義常量
/**
* 定義后端接口狀態(tài)碼
*/
const codeStatus = {
_SUCCESS_:'200',//請求成功
_TOKEN_INVALID_:'40006',//token失效
}
const codeMessage:any = {
200: '服務(wù)器成功返回請求的數(shù)據(jù)。',
201: '新建或修改數(shù)據(jù)成功。',
202: '一個請求已經(jīng)進入后臺排隊(異步任務(wù))。',
204: '刪除數(shù)據(jù)成功。',
400: '發(fā)出的請求有錯誤,服務(wù)器沒有進行新建或修改數(shù)據(jù)的操作。',
401: '用戶沒有權(quán)限(令牌、用戶名、密碼錯誤)。',
403: '用戶得到授權(quán),但是訪問是被禁止的。',
404: '訪問路徑錯誤',
406: '請求的格式不可得。',
410: '請求的資源被永久刪除,且不會再得到的。',
422: '當(dāng)創(chuàng)建一個對象時,發(fā)生一個驗證錯誤。',
500: '服務(wù)器發(fā)生錯誤,請檢查服務(wù)器。',
502: '網(wǎng)關(guān)錯誤。',
503: '服務(wù)不可用,服務(wù)器暫時過載或維護。',
504: '網(wǎng)關(guān)超時。',
}
狀態(tài)碼處理
fetch 對所有的 response code 包括 200X 300X 400X 500X 等返回的都是 resolve 狀態(tài), 只有拋異常時才會走到 catch 里面,該函數(shù)把 非200X 請求作為 reject 狀態(tài)區(qū)分出來,在catch中統(tǒng)一處理。
const checkHttpStatus = (response:any) => {
if (response.status >= 200 && response.status < 300) {
return response;
}
const errortext = codeMessage[response.status] || response.statusText;
//此處需要一個提示信息,待提示組件完成再完善
BaseNormalToast(errortext,{duration:5000})
const error = new Error(errortext);
error.name = response.status;
throw error;
};
/**
* 對后端接口返回的code碼進行統(tǒng)一的處理
* @param {*} response 后端返回的數(shù)據(jù)
*/
const checkCodeStatus = (response:any) => {
if(response.code == codeStatus._SUCCESS_){
}else if(response.code == codeStatus._TOKEN_INVALID_){
//token失效的一系列處理
BaseNormalToast('token失效,請重新登錄??!',{duration:5000})
}else{//其他普通錯誤碼
BaseNormalToast(`錯誤碼:${response.code},錯誤信息:${response.message}`,{duration:5000})
}
return response
};
超時處理
/**
* 讓fetch也可以timeout
* timeout不是請求連接超時的含義,它表示請求的response時間,包括請求的連接、服務(wù)器處理及服務(wù)器響應(yīng)回來的時間
* fetch的timeout即使超時發(fā)生了,本次請求也不會被abort丟棄掉,它在后臺仍然會發(fā)送到服務(wù)器端,只是本次請求的響應(yīng)內(nèi)容被丟棄而已
* @param {Promise} fetch_promise fetch請求返回的Promise
* @param {number} [timeout=10000] 單位:毫秒,這里設(shè)置默認超時時間為10秒
* @return 返回Promise
*/
function timeout_fetch(fetch_promise:any,timeout = 10000) {
let timeout_fn:any = null;
//這是一個可以被reject的promise
let timeout_promise = new Promise(function(resolve, reject) {
timeout_fn = function() {
reject('timeout promise');
};
});
//這里使用Promise.race,以最快 resolve 或 reject 的結(jié)果來傳入后續(xù)綁定的回調(diào)
let abortable_promise = Promise.race([
fetch_promise,
timeout_promise
]);
setTimeout(function() {
timeout_fn();
}, timeout);
return abortable_promise ;
}
使用封裝
/**
* @param {string} url 接口地址
* @param {string} method 請求方法:GET、POST
* @param {JSON} [params=''] body的請求參數(shù),默認為空
* @param {JSON} headers headers的參數(shù),默認為{}
* @param {boolean} loading 是否需要全局的loading
* @return 返回Promise
*/
interface Options {
url : string;
method : string;
params ?: any;
headers ?: object;
}
interface FetchOptions {
method : string;
headers : any;
body ?: any;
}
function BaseFetch(options:Options){
let {url, method, params = '',headers = {}} = options
let header = {
Accept: 'application/json',
"Content-Type": "application/json;charset=UTF-8",
"x_access_token":sessionStorage.token, //用戶登陸后返回的token,某些涉及用戶數(shù)據(jù)的接口需要在header中加上token
...headers
};
method = method.toUpperCase();
let data = params
let options:FetchOptions = {
method: method,
headers: header,
}
if(params != ''){ //如果網(wǎng)絡(luò)請求中帶有參數(shù)
if(method=='GET'){
let paramsArray:any[] = []
Object.keys(params).forEach(key => paramsArray.push(key + '=' + encodeURIComponent(params[key])))
if (url.search(/\?/) === -1) {
typeof (params) === 'object' ? url += '?' + paramsArray.join('&') : url
} else {
url += '&' + paramsArray.join('&')
}
}
if(method == 'POST' || method == 'PUT' || method == 'DELETE'){
if (!(params instanceof FormData)) {
data = JSON.stringify(params);
} else {
// params是formdata
delete header["Content-Type"]
}
options = {
method: method,
headers: header,
body:data
}
}
}
return new Promise(function (resolve, reject) {
timeout_fetch(fetch(url, options))
.then(checkHttpStatus)
.then((response) => response.json())
.then(checkCodeStatus)
.then((responseData) => {
resolve(responseData);
})
.catch( (err) => {
reject(err);
});
});
}
export default BaseFetch
使用示例
import { BaseFetch } from "./Fetch";
/**
* 請求服務(wù)器的函數(shù),用于請求數(shù)據(jù)
* @param {*} params 請求數(shù)據(jù)時需要傳遞的參數(shù)
*/
export async function login(params: any) {
return BaseFetch({
url: 'login',
method: "POST",
params
});
}
歡迎關(guān)注微信公眾號:猴哥說前端