都过了多少个愚人节了,还用担心axios是否适合自己的业务?
2020/4/2 11:02:17
本文主要是介绍都过了多少个愚人节了,还用担心axios是否适合自己的业务?,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
封装 axios 请求大同小异,本文提供思路和源码,不要以偏概全,适合自己业务才是最重要的!
功能点实现
- 基于 Restful 风格进行封装
- 取消请求与拦截重复请求
- token 拦截
- 接口白名单
- token 自动登录(看自己个人业务是否需要,一般用于移动端)
- 导出封装 GET/POST/DELETE/PUT/PATCH/...请求函数
话不多说,万事总要开头,先来引入开头
import axios from 'axios' 复制代码
1. 基于 Restful 风格进行封装
GET和POST明显的区别就是参数放置位置不同,前者放在
url?
后面,后者放在body
体内。
然鹅,Restful 风格在其基础添加了DELETE、PUT和PATCH请求类型,其中DELETE请求参数和GET一样放在
url?
后面,PUT、PATCH这两个的请求参数则是和POST一样放在body
体内。至于怎么放,看你们规范怎么定
至于想怎么改更请求参数放置位置,请继续往下看!
类型 | 请求参数放置位置 | 含义 |
---|---|---|
GET |
url? 后面 |
请求资源(查询用户) |
DELETE |
url? 后面 |
删除数据(删除用户) |
POST |
body 体内 |
创建数据(创建用户) |
PUT |
body 体内 |
更新全部数据(修改用户信息,昵称,签名,邮箱等等...全部更新) |
PATCH |
body 体内 |
更新部分数据(修改用户状态) |
先创建一个axios
实例,然后为其赋值默认参数。
const service = axios.create({ // 设置默认请求头 // 比如 BASE_API = 'api' // 那么访问就是 {你当前的域名}/api/{接口地址} http://localhost:8080/api/users baseURL: process.env.BASE_API, // 这里因为我后台把 http状态码 跟 定制返回体状态码 看为一样的,所以 http.status === result.status validateStatus: status => status < 500, // 拦截状态码大于500 // 修改默认请求头,采用json格式传输,对数组对象极其方便,不用专门下qs格式化参数 // common对应的是参数放在请求url上(get/delete),patch/post/put参数放在body体内 // 这里对应http Request Header.Accept headers: { common: { Accept: 'application/json; charset=UTF-8' }, patch: { 'Content-Type': 'application/json; charset=UTF-8' }, post: { 'Content-Type': 'application/json; charset=UTF-8' }, put: { 'Content-Type': 'application/json; charset=UTF-8' } }, timeout: 60000 // 请求超时时间 }) 复制代码
2. 取消请求与拦截重复请求
官方原话:Axios 的 cancel token API 基于cancelable promises proposal,它还处于第一阶段。
使用
axios.CancelToken
进行拦截
// 每次请求都会记录在此对象里,用于判断是否重复 const pending = {} // axios.CancelToken const { CancelToken } = axios const paramsList = ['get', 'delete'] const dataList = ['post', 'put', 'patch'] // 区分参数位置 const isTypeList = method => { if (paramsList.includes(method)) { return 'params' } else if (dataList.includes(method)) { return 'data' } } /** * 获取请求唯一值(key) * @param {Object} config - axios拦截器的config * @param {Boolean} isResult - 截取url唯一,这里区别请求前和请求后,因为前者和后者的url不同,所以需要区分一下 */ function getRequestIdentify(config, isResult = false) { const url = !isResult ? config.url : config.baseURL + config.url.substring(1, config.url.length) const params = { ...(config[isTypeList(config.method)] || {}) } delete params.t return encodeURIComponent(url + JSON.stringify(params)) } /** * 每次请求前 清除上一个跟它相同的还在请求中的接口 * @param {String} key - url唯一值 * @param {Boolean} isRequest - 是否执行取消重复请求 */ function removePending(key, isRequest = false) { if (pending[key] && isRequest) { pending[key]('取消重复请求') } delete pending[key] } 复制代码
请求前的 url
![请求前的url](/upload/202004/02/202004021102177512.png)
请求后的 url
![请求后的url](/upload/202004/02/202004021102177971.png)
在拦截器里面设置
// 请求前 service.interceptors.request.use( config => { // 获取该次请求的唯一值 const requestData = getRequestIdentify(config, true) // 删除上一个相同的请求 removePending(requestData, true) // 实例化取消请求,并同时注入pending config.cancelToken = new CancelToken(c => { pending[requestData] = c }) return config }, error => { Promise.reject(error) } ) // 请求完成后 service.interceptors.response.use( response => { // 把已经完成的请求从 pending 中移除 const requestData = getRequestIdentify(response.config) removePending(requestData) // ... }, error => { // ... } ) 复制代码
3. token 拦截
token
是比较常见的前后端校验拦截方式
// 引入store import store from 'store' service.interceptors.request.use(config => { // ...上面的代码 const token = store.getters.GET_TOKEN if (!token) { // 取消该次请求 pending[requestData]('cancelToken') store.dispatch('logOut') } else { // 在业务约定的headers里面某个字段定义token,方便后端提取校验 config.headers['Authorization'] = token } return config }) 复制代码
4. 接口白名单
所谓白名单,就是不用任何校验权限(token)的接口,比如用户登录、用户注册、修改用户密码等等
// 定义接口白名单 const noLogin = [ // ... '/login', '/register' ] service.interceptors.request.use(config => { // ...上面代码 if (!noLogin.includes(config.url.replace(config.baseURL, ''))) { // 当不在白名单内时则校验token // ...上面代码 } return config }) 复制代码
5. token 自动登录
通过每次请求完成后,如果该请求返回的是登录失效(
401
),则用一个数组装在该次的返回对象config
。
然后重启发起该获取 token 请求,完成后把数组 map 重新发起一次请求,然后清除数组
前提是把账号密码记录存储在浏览器或者有刷新 token 接口
准备好以下这些方法
// 是否正在刷新的标记 // const isRefreshing = false // 重试队列,每一项将是一个待执行的函数形式 // const retryRequests = [] // 重新请求流程处理 async function reRequest(response) { const { config } = response if (!isRefreshing) { isRefreshing = true const reRes = await refreshTokenFn() if (reRes) { config.headers['Authorization'] = store.getters.GET_TOKEN // 已经刷新了token,将所有队列中的请求进行重试 retryRequests.map(cb => cb(store.getters.GET_TOKEN)) // 清空列队 retryRequests = [] config.baseURL = '' isRefreshing = false return service(config) } else { // 刷新token失败,跳回登录页 store.dispatch('logOut') } isRefreshing = false } else { return new Promise(resolve => { // 将resolve放进列队,用一个函数形式保存,等token刷新后直接执行 retryRequests.push(token => { config.baseURL = '' config.headers['Authorization'] = token resolve(service(config)) }) }) } } // 刷新token方法 async function refreshTokenFn() { try { const { loginName, password } = store.getters.GET_USER_INFO // 自己定义的获取方法 const res = await store.dispatch('getToken', { loginName, password }) return res } catch (error) { return false } } 复制代码
使用
// 请求前 service.interceptors.request.use(config => { const token = store.getters.GET_TOKEN if (!token) { // 当token不存在时,自动重新发起请求 return reRequest({ config }) } else { config.headers['Authorization'] = token } return config }) service.interceptors.response.use(response => { const res = response.data if (res.status === 401) { // 登录失效 // 重新刷新token并发起请求 return reRequest(response) } return data }) 复制代码
6. 导出封装 GET/POST/DELETE/PUT/PATCH/...请求函数
自定义请求参数位置,在使用实例好的
service(params)
时,通过传参数的 params 或者 data 决定放置位置
如果有传参数,则会覆盖默认的
参数 | 含义 | 默认值 |
---|---|---|
url | 请求地址 | —— |
method | 请求类型,有 GET/POST/DELETE/PUT/PATCH 可选 | GET |
params | 请求参数在url? 后面时,则把参数放在 params,适用于 GET/DELETE |
null |
data | 请求参数在body 体内时,则把参数放在 data 里,适用于 POST/PUT/PATCH |
null |
headers | 请求头,跟axios.create({ headers }) 一样 |
—— |
responseType | 服务器响应的数据类型,可选'arraybuffer', 'blob', 'document', 'json', 'text', 'stream' | json |
/** * get请求方法 * @export axios * @param {String} url - 请求地址 * @param {Object} params - 请求参数 * @returns */ export const get = (url, params = {}) => { params.t = new Date().getTime() // get方法加一个时间参数,解决ie下可能缓存问题. return service({ url: url, method: 'GET', params }) } /** * delete请求方法 * @export axios * @param {String} url - 请求地址 * @param {Object} params - 请求参数 * @returns */ export const del = (url, params = {}) => { params.t = new Date().getTime() // get方法加一个时间参数,解决ie下可能缓存问题. return service({ url: url, method: 'DELETE', params }) } /** * post请求方法 * @export axios * @param {String} url - 请求地址 * @param {Object} data - 请求参数 * @returns */ export const post = (url, data = {}) => { return service({ url, method: 'POST', data }) } /** * put请求方法 * @export axios * @param {String} url - 请求地址 * @param {Object} data - 请求参数 * @returns */ export const put = (url, data = {}) => { return service({ url, method: 'PUT', data }) } /** * patch请求方法 * @export axios * @param {String} url - 请求地址 * @param {Object} data - 请求参数 * @returns */ export const patch = (url, data = {}) => { return service({ url, method: 'PATCH', data }) } /** * 当以上方法不满足,则自定义参数和配置请求 * @param {Object} options */ export const fetch = options => service(options) 复制代码
源码
import axios from 'axios' import store from 'store' import { Message, MessageBox } from 'element-ui' // 创建axios实例 const service = axios.create({ baseURL: process.env.BASE_API, validateStatus: status => status < 500, // 拦截状态码大于500 headers: { common: { Accept: 'application/json; charset=UTF-8' }, patch: { 'Content-Type': 'application/json; charset=UTF-8' }, post: { 'Content-Type': 'application/json; charset=UTF-8' }, put: { 'Content-Type': 'application/json; charset=UTF-8' } }, timeout: 60000 // 请求超时时间 }) const paramsList = ['get', 'delete', 'patch'] const dataList = ['post', 'put'] const isTypeList = method => { if (paramsList.includes(method)) { return 'params' } else if (dataList.includes(method)) { return 'data' } } const pending = {} const CancelToken = axios.CancelToken const removePending = (key, isRequest = false) => { if (pending[key] && isRequest) { pending[key]('取消重复请求') } delete pending[key] } const getRequestIdentify = (config, isResult = false) => { let url = config.url if (isResult) { url = config.baseURL + config.url.substring(1, config.url.length) } const params = { ...(config[isTypeList(config.method)] || {}) } delete params.t return encodeURIComponent(url + JSON.stringify(params)) } // 不需要token的接口 const noLogin = [ '/account/random', '/account/token', '/account/defaultPassword', '/account/changeDefaultPassword' ] // 是否正在刷新的标记 // const isRefreshing = false // 重试队列,每一项将是一个待执行的函数形式 // const retryRequests = [] // request拦截器 service.interceptors.request.use( config => { const requestData = getRequestIdentify(config, true) removePending(requestData, true) config.cancelToken = new CancelToken(c => { pending[requestData] = c }) if (!noLogin.includes(config.url.replace(config.baseURL, ''))) { const token = store.getters.GET_TOKEN if (!token) { // 当token不存在时,自动重新发起请求 // return reRequest({ config }) // 取消该次请求 pending[requestData]('cancelToken') store.dispatch('logOut') } else { config.headers['Authorization'] = token } } // 处理为空的参数,设置为null handlerNullParams(config) return config }, error => { Promise.reject(error) } ) // response拦截器 service.interceptors.response.use( response => { // 把已经完成的请求从 pending 中移除 const requestData = getRequestIdentify(response.config) removePending(requestData) const res = response.data if (res.status === 401) { // 登录失效 MessageBox.alert('登录失效,请重新登录', '权限提示', { confirmButtonText: '退出', callback: () => store.dispatch('logOut') }) // 重新刷新token并发起请求 // return reRequest(response) } else if (res.status !== 200) { Message.error(res.msg || '系统异常') return Promise.reject(res.msg || '系统异常') } return res }, error => { if ( !( error && (error.message === '取消重复请求' || ~error.message.indexOf('cancelToken')) ) ) { if (error.code === 'ECONNABORTED') { Message.error('请求超时') } else if (error && error.response) { // error.response.status Message.error(error.response.data.msg || '系统异常') } else { Message.error('系统异常') console.log(error) } } return Promise.reject(error) } ) export default service // 重新请求流程处理 // async function reRequest(response) { // const { config } = response // if (!isRefreshing) { // isRefreshing = true // const reRes = await refreshTokenFn() // if (reRes) { // config.headers['Authorization'] = store.getters.GET_TOKEN // // 已经刷新了token,将所有队列中的请求进行重试 // retryRequests.map(cb => cb(store.getters.GET_TOKEN)) // // 清空列队 // retryRequests = [] // config.baseURL = '' // isRefreshing = false // return service(config) // } else { // // 刷新token失败,跳回登录页 // store.dispatch('logOut') // } // isRefreshing = false // } else { // return new Promise((resolve) => { // // 将resolve放进列队,用一个函数形式保存,等token刷新后直接执行 // retryRequests.push(token => { // config.baseURL = '' // config.headers['Authorization'] = token // resolve(service(config)) // }) // }) // } // } // // 刷新token方法 // async function refreshTokenFn() { // try { // const { loginName, password } = store.getters.GET_USER_INFO // const res = await store.dispatch('getToken', { loginName, password }) // return res // } catch (error) { // return false // } // } /** * get请求方法 * @export axios * @param {String} url - 请求地址 * @param {Object} params - 请求参数 * @returns */ export const get = (url, params = {}) => { params.t = new Date().getTime() // get方法加一个时间参数,解决ie下可能缓存问题. return service({ url: url, method: 'GET', params }) } /** * delete请求方法 * @export axios * @param {String} url - 请求地址 * @param {Object} params - 请求参数 * @returns */ export const del = (url, params = {}) => { params.t = new Date().getTime() // get方法加一个时间参数,解决ie下可能缓存问题. return service({ url: url, method: 'DELETE', params }) } /** * post请求方法 * @export axios * @param {String} url - 请求地址 * @param {Object} data - 请求参数 * @returns */ export const post = (url, data = {}) => { return service({ url, method: 'POST', data }) } /** * put请求方法 * @export axios * @param {String} url - 请求地址 * @param {Object} data - 请求参数 * @returns */ export const put = (url, data = {}) => { return service({ url, method: 'PUT', data }) } /** * patch请求方法 * @export axios * @param {String} url - 请求地址 * @param {Object} data - 请求参数 * @returns */ export const patch = (url, data = {}) => { return service({ url, method: 'PATCH', data }) } /** * post上传文件请求方法 * !! 必须使用formData方式 * @export axios * @param {String} url - 请求地址 * @param {Object} data - 请求参数 * @returns */ export const postFile = (url, data = {}) => { return service({ url, method: 'POST', data, headers: { 'Content-Type': 'multipart/form-data' }, timeout: 1000 * 60 * 3 }) } /** * get导出文件 * @export axios * @param {String} url - 请求地址 * @param {Object} data - 请求参数 * @returns */ export const getExport = (url, params = {}) => { return service({ url, method: 'GET', params, responseType: 'blob', timeout: 1000 * 60 * 3 }) } /** * 当以上方法不满足,则自定义参数和配置请求 * @param {Object} options */ export const fetch = options => service(options) 复制代码
结语
广州找工作img...简历仿佛入了海底一下...
这篇关于都过了多少个愚人节了,还用担心axios是否适合自己的业务?的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-03-14system bios shadowed
- 2024-03-14gabios
- 2024-02-07iOS应用提交上架的最新流程
- 2024-02-06打包 iOS 的 IPA 文件
- 2023-12-07uniapp打包iOS应用并通过审核:代码混淆的终极解决方案 ?
- 2023-11-25uniapp IOS从打包到上架流程(详细简单) 原创
- 2023-11-10【iOS开发】iOS App的加固保护原理:使用ipaguard混淆加固
- 2023-09-30最强大的iOS应用源码保护工具:Ipa Guard,保护你的商业机密代码
- 2023-09-07iOS安全加固探讨:代码混淆、类名方法名混淆等方法
- 2023-09-05iOS代码加固与保护方法详解 - 提升iOS应用安全性的关键步骤