猴子也能看懂的axios封装

1,439 阅读2分钟

大家有没有发现,在我门前端的工作中总是存在着大量的请求,数据处理,对接后台接口的工作,至少在我的工作中对接口基本是每天都在发生的事情,工作中最经常用到的库当然就是axios了,我们这篇文章就从0到1,好好的认识一下axios并且学会如何在项目中优雅的使用axios。

准备好了吗要开始了.jpeg

简单认识Axios,Axios到底是个啥?

  • Axios是一个基于Promise封装的HTTP库,打个比方,就像是Jquery里面的Ajax一样,用于HTTP请求。(这里肯定有人要问我Axios能不能请求https了,咱们y1s1,可以请求,但是由于Axios的证书是自签发的,浏览器并不认同,所以是不能自动加载的)

Axios有哪些特性

  • 支持Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防御XSRF

Axios各类请求

get请求

写法一:
axios.get('/data.json',{
    params:{
        id:12
    }
}).then(res =>{ console.log(res) })

写法二:
axios({
    method:'get',
    url:'/data.json',
    params:{
        id:12
    }
}).then(res =>{
    console.log(res,返回值)
})

两种写法最终请求路径为 http://localhost:8080/data.json?id=12

post请求

传送数据类型一般分为两种 form-data:上传图片等需要使用这种表单上传的形式 application/json:这种json格式的用的很普遍,对前后端都是很友好的

写法一:
axios.post('/post',data).then(res=>{
    consol.log(res)
})

写法二:
axios.post({
    method:'post',
    url:'/post',
    params:data
}).then(res=>{
    console.log(res)
})

form-data的请求略微不同,因为我们需要传送一个form-data类型的数据
let formData = new FormData()
for(let key in data){
    formData.append(key,data[key])
}
axios.post({
    method:'post',
    url:'/post',
    params:formData
}).then(res=>{
    console.log(res)
})

put,patch请求都和上面类似,只是方法名不同

delete请求 delete请求需要前后端提前沟通这个参数是否在url后拼接

如果是正常传参,写法和上面差不多,如果需要拼接的话,需要下面这种写法:
axios.post(`/post/${params}`).then(res=>{
    console.log(res)
})

Axios处理并发请求

  • 并发请求:同时处理多个请求,并统一处理返回值
  • 用到的方法为axios.all()axios.spread()
  • axios.all()的参数为一个包含所有并发请求的数组
  • axios.spread()则作为axios.all()方法的参数,是一个回调函数
代码:
axios.all([
    axios.get('/data.json'),
    axios.post('/data2.json')
]).then(
axios.spread((dataRes,data2Res)=>{
    console.log(dataRes,data2Res)
})
)

以上代码便是axios对于并发请求的写法,有所不同的是回调函数是放在了参数axios.spread()中完成的,这点一定要区分开。并发请求的应用场景并不多,一般都用于需要同时处理多个请求返回值的情况,这个时候将多个请求放在axios.all()方法中做并发即可。

Axios的实例创建

一般对axios的封装是写在js文件中,在接口文件中进行引入

axios.create({
    baseUrl:'http://localhost:8080',    //请求的域名
    timeout:1000,       //超时时长
    url:'/data.json'    //请求路径
    method:''      //请求方法,包括get,post,put,patch,delete等
    headers:{
        token:''
    },             //请求头,一般用于存放token
    params:{
    
    },             //请求参数,放在请求体外
    data:{
                    
    }              //请求参数,放在请求体内部
})

Axios请求的分类

  • axios在全局进行配置
  • axios可以对实例进行配置
  • axios对请求进行配置
  • 三者的优先级从低到高依次是全局配置 < 实例配置 < 请求配置 axios全局配置
一般对于axios的全局配置经常用的就两个,如下
axios.defaults.baseUrl = 'http://'
axios.defaults.timeout = 5000

axios对实例配置

let instance = axios.create()

如果此时不对axios实例做任何配置,则会默认继承axios全局配置中的参数,如果需要做修改的话,写法如下:
instance.defaults.timeout = 1000

这样的话就会自定义修改掉axios实例的全局配置,未修改的依然会继承到

axios请求配置

instance.get('data.json',{
    timeout:10000
})
此时的请求配置是优先级最高的,超时设置将定为10000ms

实际开发中,axios的全局配置是很少被用到的,一般实际开发中都会声明一个axios的实例,在实例中对axios进行相应的配置。这里说明一下,实例是可以存在多个的,这种情况一般用于服务很多的情况下。

举例:

实际开发中如果有多个后端服务,比如说现在有两个服务http://localhost:8080和http://localhost:9090

//定义实例instance1
let instance1 = axios.create({
    baseUrl:'http://localhost:8080',
    timeout:1000
}) 

//定义实例instance2
let instance2 = axios.create({
    baseUrl:'http://localhost:9090',
    timeout:5000
}) 


instance1.get('/contactList',{
    page:1,
    size:20
}).then((res) =>{
    console.log(res.data)
})

以上方法用到了 
baseUrl='http://localhost:8080'
timeout = 1000
url = '/contactList'
method = 'get'
params = { page:1 , size:20 }


instance2.get('/orderList',{
    page:1,
    size:1000
}).then((res) =>{
    console.log(res.data)
})

以上方法用到了 
baseUrl='http://localhost:9090'
timeout = 5000
url = '/orderList'
method = 'get'
params = { page:1 , size:1000 }



当然,实例中的参数是可以在特定的接口中完成重写的,比如一个接口获取的数据量比较大的时候,我们就可以重写axios实例的参数。如下:

instance2.get(
'/orderList',
{ page:1 , size:1000 },
{ timeout:10000 }
).then((res) =>{
    console.log(res.data)
})

以上方法用到了 
baseUrl='http://localhost:9090'
timeout = 10000
url = '/orderList'
method = 'get'
params = { page:1 , size:1000 }

这里我们timeout参数并不是我们想定义多大就定义多大,具体需要后端提供给我们参考依据

Axios的拦截器

拦截器的作用就是在请求发生之前或者响应数据返回后做一些特殊的处理,所以拦截器分为两种,一种是请求拦截器,一种是响应拦截器。

  • 请求拦截器
axios.interceptors.request.use(
    (config) => {
        //在发送请求之前做些什么
        return config  //必须返回config参数
    },(error) =>{
        //在请求错误的时候做些什么 ( 请求并未到达后端报错 )
        return Promise.reject(error)  //结果会return到请求后的.catch回调参数
    }
)
  • 响应拦截器
axios.interceptors.response.use(
    (res) => {
        //请求成功之后对返回值做什么
        return res  //同样必须返回res参数,这样才能在请求中拿到then后的res值
    },(error) =>{
        //响应错误的时候做些什么 ( 请求已经到达后端返回错误 )
        return Promise.reject(error)  //结果会return到请求后的.catch回调参数
    }
)
  • 取消拦截器
let interceptors = axios.interceptors.request.use(
    (config) => {
        config.headers = {
            auth:ture
        }
        return config
    },(error) =>{
        return Promise.reject(error)
    }
)
axios.intercepetors.request.eject(interceptors)  //取消掉拦截器的功能,需要用变量接收拦截器才可以

在实际开发中,我们碰到的开发需求大概是登录得时候接收一下token对吧,这个时候就可以在我们的请求拦截器中把token放在config的headers中。

axios.interceptors.request.use(
    (config) => {
        config.headers.token = ''
        
        return config
    },(error) =>{
        return Promise.reject(error)
    }
) 

当然项目中假设有无需登录就可使用的功能,这个时候我们可以再生成一个新的axios实例,这个实例不去做登录拦截,相对来说无需登录就能实现的功能就可以用这个axios实例来做。

Axios中对请求错误的处理

一般我们在实际开发中,我们会统一的返回错误,这个时候我们就在上述所说的拦截器实例上添加捕获错误之后所要执行的方法,这个错误处理我们放在拦截器error的回调函数中。

常见错误代码如下:

let instance = axios.create({})

//状态码一般4开头,常见错误为 401超时 和 404 not found
axios.interceptors.request.use(
    (config) => {
        return config  
    },(error) =>{
        //这块写给用户的提示,根据错误类型的不同提示不同
        
        return Promise.reject(error)
    }
)

//状态码一般5开头,常见错误为 500系统错误 和 502系统重启
axios.interceptors.response.use(
    (res) => {
        return res 
    },(error) =>{
         //这块写给用户的提示,根据错误类型的不同提示不同
         
        return Promise.reject(error)
    }
)

Axios取消请求

let source = axios.CancelToken.source()

axios.get('/data.json',{
    CancelToken:source.token
}).then(res =>{
    console.log(res)
}).catch(err =>{
    console.log(err)
})

source.cancel('cancel http')  //这个方法会直接进入catch方法中

这里肯定会有人问,我们什么时候会用到axios的取消请求? 举个例子,比如很多CRM管理系统,涉及到大批量数据的查询,可能查询数据需要5s左右,但是过了5s还没查询到,用户这个时候不想查了,去到了其他页面,这个时候我们是不是应该手动取消一下上一个请求啊,这样也算是释放不必要用到的网络资源。

Axios对各类请求做封装

我们为什么要对axios请求做进一步的封装? 因为在我们的项目中,可能存在大量的逻辑处理,比如给用户的提示等等,接口很多的情况下就会造成代码的冗余,这个时候我们可以统一封装请求,只在封装中写一遍就可以完成。一般提示文案也是由后端返回的。

我封装的操作如下: vue为例,在src目录下新建api文件夹,文件夹下建立http.js文件和index.js文件,并建立module文件夹,文件夹中根据各模块业务的不同建立不同的js文件,http.js中就是我们对axios的封装,之后module下的文件都是需要引入的,index.js中可以做module下所有接口的统一导出,当然如果不需要统一导出的话,可以在每个页面中按需引入自己所需要的方法。

直接贴代码:

module某一个文件home.js:

import { get,post,put,delete } from './http'

export const  getData = (params) => {
    return get('/getlist',params)
}

export const  postData = (params) => {
    return post('/postData',params)
}

export const  putData = (params) => {
    return put('/putData',params)
}

export const  deleteData = (params) => {
    return delete('/deleteData',params)
}


http.js:

import axios from 'axios'

const config = {
    baseURL: ['dev', 'test', 'production'].includes(process.env.NODE_ENV) baseUrl.serviceUrl : '/',   //根据环境变量去选择服务
    timeout: 30 * 1000,
    responseType: "json"  //返回值类型
};
const instance = axios.create(config)
 
instance.interceptors.request.use(
    (config) => {
        //可以添加请求头,具体看后端约定
        return config 
    },(error) =>{
        return Promise.reject(error) 
    }
)

instance.interceptors.response.use(
    (res) => {
        return res 
    },(error) =>{
        return Promise.reject(error)
    }
)


/**
 * 封装get请求
 * @param url
 * @param params
 * @returns {Promise}
 */
export function get(url, params) {
    return new Promise((resolve, reject) => {
        instance.get(url, {
            params,
            headers:{
                'Content-Type': 'application/json;charset=UTF-8'
            }
        },
        
        ).then(response => {
            resolve(response.data);
        })
            .catch(error => {
                reject(error)
            })
    })
}

/**
 * 封装post请求
 * @param url
 * @param params
 * @returns {Promise}
 */
export function post(url, params) {
    return new Promise((resolve, reject) => {
        instance.post(url, params).then(response => {
            resolve(response.data);
        }).catch(error => {
            reject(error)
        })
    })
}

/**
 * 封装put请求
 * @param url
 * @param params
 * @returns {Promise}
 */
export function put(url, params) {
    return new Promise((resolve, reject) => {
        instance.put(url, params)
            .then(response => {
                resolve(response.data);
            }).catch(error => {
                reject(error)
            })
    })
}

/**
 * 封装delete请求
 * @param url
 * @param params
 * @returns {Promise}
 */
export function axiosDelete(url, params) {
    return new Promise((resolve, reject) => {
        instance.delete(url,params)
            .then(response => {
                resolve(response.data);
            }).catch(error => {
                reject(error)
            })
    })
}

当然我们也可以做一个统一的封装方法,根据不同类型来做封装,这里直接贴代码:

export function request( timeout = 3000 , url , params , method) {
    switch(method){
      case 'get'
        headers = {
                token:''
          }
      break;
      case 'post'
       headers = {
                token:''
          }
      break;
      case 'delete'
       headers = {
                token:''
          }
      break;
      case 'put'
       headers = {
                token:''
          }
      break;
      let obj = {
          url: url,
          method: method
          params
      }
      return new Promise((resolve , reject)) => {
          instance(obj).then(res=>{
              if(type == 'get'){
                  //根据方法不同做定制化操作
                  return resolve(res)
              }
               if(type == 'post'){
                  //根据方法不同做定制化操作
                  return resolve(res)
              }
               if(type == 'put'){
                  //根据方法不同做定制化操作
                  return resolve(res)
              }
               if(type == 'delete'){
                  //根据方法不同做定制化操作
                  return resolve(res)
              }
          }).catch(err => {
              根据type值做错误提示定制
          })
      }
    }
}

api接口的统一导出

我们可以对我们module中的api进行统一导出引入,利用webpack中的require.context()方法即可

const apiFiles = require.context("./model", false, /\.js$/);
var arryApi = {};
apiFiles.keys().forEach((key)=>{
    for(var i in apiFiles(key).default){
        arryApi[i] = apiFiles(key).default[i]
    }
});
export default arryApi