Axios封装

164 阅读2分钟
  • Axios 是一个基于 promise 网络请求库,作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。

  • 封装希望达到的效果

    • 引用方便,在组件中,通过this.$http使用 ✅
    • 兼容 REST 风格封装,使用 JSON 进行交互,提供常用的四种方法 ✅
    • 不同的请求方法,参数格式一直 this.$http.get(url,params)、this.$http.post(url,params)
    • 错误时,控制台有特别的日志输出,要是公司有条件,可提交到服务器,方便排查问题 ✅
    • 统一处理HTTP状态码和跟后端约定好的响应状态码,断网时显示断网组件等 ✅
    • 取消请求 ✅
      • 重复请求 ✅
      • 用户手动取消 ✅
      • 清空全部请求 ✅
    • 文件下载 ⭕️
    • 跨域配置 ✅
    • 响应拦截 ⇒ 将http响应信息存入在httpData 属性中,与服务响应信息一起返回 ✅
    • mock数据 ⇒ 使用adapter自定义处理请求,mock服务端数据 ✅
  • 封装定义

  • src/libs/axios.js
    
    import axios from 'axios'
    import { message as $Message } from 'ant-design-vue';
    
    
    import { getToken } from '@/utils/tools'
    import { logInfo, redLog } from '@/utils/log'
    
    const baseURL = window.location.protocol + '//xxxx'
    
    const instance = axios.create({
      baseURL,
      timeout: 10000 * 5,
      headers: {
        Authorization: `xxxx ${getToken()}`
      }
    });
    // 请求拦截器
    const interceptorsRequest = (config) => {
      console.log('请求拦截器',config)
      return config
    }
    // 请求拦截器-错误处理
    const interceptorsRequestFailed = (error) => {
      return Promise.reject(error)
    }
    // 响应拦截器
    const interceptorsResponse = (res) => {
      console.log('响应拦截器',res)
      return res
    }
    // 响应拦截器-错误处理
    const interceptorsResponseFailed = (error) => {
      // 上传错误日志
      logInfo(error)
      redLog(error)
      return Promise.reject(new Error(error.message))
    }
    
    instance.interceptors.request.use(interceptorsRequest,interceptorsRequestFailed)
    instance.interceptors.response.use(interceptorsResponse,interceptorsResponseFailed)
    
    export default function request(e) {
      const { url = '', method = 'GET', data = {}, config = {}, option = {}} = e
    
      switch (method.toUpperCase()) {
        case 'GET':
          return instance.get(url, { params: data, ...config })
        case 'POST':
          return instance.post(url, data, config)
      }
    }
    
    
  • 使用

  • import request from '../libs/axios'
    
    
    // @data 参数 Object 
    // @config 请求配置
    export const postRequest = (data, config) => {
      return request({
    		// 必填,请求地址
        url: '/api/user/media_account/list_shop_account', 
    		// 请求参数(get\post)都是一个属性
        data,
    		// 请求方式,默认get
        method: 'POST',
    		// 请求配置
        config: {
    			// 传入配置
    			...config,
    			// 支持请求配置 https://axios-http.com/zh/docs/req_config
    		},
    		// 自定义配置项
    		option: {}
      })
    }
    
    
    
  • 使用 adapter mock请求

  • import axios  from "axios"
    const mockData = new Map([
        ['/api/user/adapter/mock', { status: 200, statusText: 'OK', data: { message: '我是mock数据' } }]
    ])
    
    export default function (config) {
        // 判断是否存在mock数据
        let has = mockData.has(config.url)
    
        // 调用默认请求接口, 发送正常请求及返回
        if (!has) {
            // 删除配置中的 adapter, 使用默认值
            delete config.adapter
            // 通过配置发起请求
            return axios(config)
        }
        return Promise.resolve(Object.assign({ headers: config.headers }, mockData.get(config.url)))
    }
    
    // axios.js
    import getAdapter from './axios-adapter';
    const instance = axios.create({
    	// ...
    	// 使用
      adapter: getAdapter
    });
    
  • 取消请求

  • 可以分为三种情况:重复请求、用户手动取消、页面切换取消

  • 重复请求

    • Axios 如何取消重复请求?
    • jaimport Qs from 'qs'
      import axios from 'axios'
      const pendingRequest = new Map();
      
      
      export function addPendingRequest(config) {
          const requestKey = generateReqKey(config);
          config.cancelToken =
              config.cancelToken ||
              new axios.CancelToken((cancel) => {
                  if (!pendingRequest.has(requestKey)) {
                      pendingRequest.set(requestKey, cancel);
                  }
              });
      }
      
      export function removePendingRequest(config, src) {
          const requestKey = generateReqKey(config);
          if (pendingRequest.has(requestKey)) {
              const cancel = pendingRequest.get(requestKey);
              cancel(requestKey);
              pendingRequest.delete(requestKey);
          }
      }
      export function removePending(config) {
          const requestKey = generateReqKey(config);
          if (pendingRequest.has(requestKey)) {
              pendingRequest.delete(requestKey);
          }
      }
      
      
      function generateReqKey(config) {
          // 当请求方式、请求URL地址和请求参数都一样时,我们就可以认为请求是一样的
          const { method = 'get', url, params, data } = config;
          // let data = typeof config.data === 'string' ? JSON.parse(config.data) : config.data
          return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
      }
      
      window.pendingRequest = pendingRequest
      
  • 手动取消

    • let controller = null;
      
      <script>
      	methods: {
      		sendResponse() {
      			// 传入给配置项
      			this.$http.user.getUser({}, {signal: controller.signal}) {
      			}
      		},
      		cancelResponse() {
      			controller.abort()
      		}
      	}
      </script>
      
      
  • 清空全部

  • window.pendingRequest.forEach((value,key) =>{
        console.log(value(key))
    })
    window.pendingRequest = new Map()
    
  • 完整封装

  • import axios from 'axios'
    import { message as $Message } from 'ant-design-vue';
    // 引自定义处理请求,这使测试更加容易(mock接口)
    import getAdapter from './axios-adapter';
    // 添加请求响应拦截,处理重复请求。
    import { addPendingRequest, removePendingRequest } from './axios-helper'
    // 引入方法
    import { getToken, LogOut } from '@/utils/tools'
    // 引入日志处理
    import { logInfo, redLog } from '@/utils/log'
    // 定义基础URL
    const baseURL = window.location.protocol + ''
    
    
    const instance = axios.create({
      baseURL,
      timeout: 10000 * 5,
      headers: {
        Authorization: `xxxx ${getToken()}`
      },
      adapter: getAdapter
    });
    // 请求拦截器
    const interceptorsRequest = config => {
      removePendingRequest(config, '请求') // 检查是否存在重复请求,若存在则取消已发的请求
      addPendingRequest(config) // 把当前请求添加到pendingRequest对象中
      return config
    }
    // 请求拦截器-错误处理
    const interceptorsRequestFailed = error => {
      return Promise.reject(error)
    }
    // 响应拦截器
    // 2xx 范围内的状态码都会触发该函数。
    const interceptorsResponse = res => {
      removePendingRequest(res.config, '响应-成功'); // 从pendingRequest对象中移除请求
      if (res.data.code === 200 || res.data.success) {
        // 添加http状态并返回
        res.httpData = {
          status: res.status,
          statusText: res.statusText,
        }
        return res.data
      }
      else {
        let message = res.data.message || res.data.errorMessage
        // 响应状态码
        switch (res.data.code) {
          case 404:
            break
        }
    
        $Message.destroy()
        $Message.error(message)
        return Promise.reject(message)
      }
    }
    // 响应拦截器-错误处理
    // 超出 2xx 范围的状态码都会触发该函数。
    const interceptorsResponseFailed = error => {
      removePendingRequest(error.config || {}, '响应失败'); // 从pendingRequest对象中移除请求
      if (axios.isCancel(error)) {
        console.log('已取消重复请求: ' + error)
      }
      else {
        // HTTP 状态码
        switch (error.status) {
          case 401:
            $Message.error('平台不可重复登录,请刷新页面重新登录')
            LogOut()
          case 500:
            error.message = '服务器错误(500)'
            break
          case 502:
            error.message = '网络错误(502)'
        }
        if (!window.navigator.onLine) {
          error.message = '请检查网络链接'
        }
        // 上传错误日志
        logInfo(error)
        redLog(error)
      }
      return Promise.reject(error)
    }
    
    
    
    instance.interceptors.request.use(interceptorsRequest, interceptorsRequestFailed)
    instance.interceptors.response.use(interceptorsResponse, interceptorsResponseFailed)
    
    export default function request(e) {
      const { url = '', method = 'GET', data = {}, config = {}, option = {} } = e
    	// 自定义配置项
      // if (option) {}
      switch (method.toUpperCase()) {
        case 'GET':
          return instance.get(url, { params: data, ...config })
        case 'POST':
          return instance.post(url, data, config)
      }
    }ja
    
  • 知识点

    • Axios 响应拦截, HTTP状态码范围---默认范围
    • axios.interceptors.response.use(function (response) {
          // 2xx 范围内的状态码都会触发该函数。
          // 对响应数据做点什么
          return response;
        }, function (error) {
          // 超出 2xx 范围的状态码都会触发该函数。
          // 对响应错误做点什么
          return Promise.reject(error);
       );
      
    • 使用config.validateStatus 定义了对于给定的 HTTP状态码是 resolve 还是 reject promise
    • {
          validateStatus: function (status) {
          return status >= 200 && status < 300; // 默认值
        }
      }
      
  • 参考文档