封装Axios,一次封装团队受益,省时又省力

1,545 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情

引言

相信对Vue熟悉的读者对Axios不会感到陌生了,但是单纯的axios并不能满足我们日常的使用,因此很多时候我们都需要对axios进行二次封装以满足项目需求。下面,我们就来聊聊 Vue 中 axios 的封装。

Vue项目中如何封装axios

axios文件封装在目录src/utils/https.ts,对外暴露callApi函数

判断HTTP状态码

一个良好展示接口实时状态的提示信息是非常重要的,一方面方便前端人员定位问题原因,在一些复杂特殊场景也可以给予用户提示引导。

此处封装errorCode共通方法,来实现解耦化,方便后期维护与代码阅读。

errorCode.ts : 用于定义返回错误code

export default {

  '400': '请求头错误',
  
  '401': '认证失败,无法访问系统资源, 请重新登录',

  '403': '当前操作没有权限',

  '404': '访问资源不存在',

  '500': '服务器端出错,

  '503': '服务不可用',
  
  'default': '系统未知错误,请反馈给管理员'

}

http.ts : 用于axios封装

import axios from 'axios'
import errorCode from '@/utils/errorCode'
import { ElMessage, ElMessageBox } from 'element-plus'

axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'

// 创建axios实例

const service = axios.create({

  // axios中请求配置有baseURL选项,表示请求URL公共部分

  baseURL: process.env.VUE_APP_BASE_API,

  // 超时

  timeout: 10000

})
// request拦截器
service.interceptors.request.use(config => {
  if (getToken() && !isToken) {
    config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }
  return config
}, error => {
    console.log(error)
    Promise.reject(error)
})


// 响应拦截器
service.interceptors.response.use(res => {
    // 未设置状态码则默认成功状态
    const code = res.data.code || 200;
    // 获取错误信息
    const msg = res.data.msg || errorCode[code] || errorCode['default']
    // 二进制数据则直接返回
    if(res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer'){
      return res.data
    }
    // 判断状态码
    if(code === 200){
      return res.data
    }else if(code === 401){
    
      //跳转首页逻辑
      
      return Promise.reject(msg)
    }else{
      ElMessage({
        message: msg,
        type: 'error'
      })
      return Promise.reject(msg)
    }
  },
  error => {  
    let { message } = error;
    if (message == "Network Error") {
      message = "后端接口连接异常";
    }
    else if (message.includes("timeout")) {
      message = "系统接口请求超时";
    }
    else if (message.includes("Request failed with status code")) {
      message = "系统接口" + message.substr(message.length - 3) + "异常";
    }
    ElMessage({
      message: message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)
export default service

防止重复请求Loading

在项目中数据异步请求是一个非常常见的场景,一个良好的Loading效果能很好的加强用户体验,如果在发起了一个请求后立即就出现一个Loading层,那么用户也就无法再次点击而造成重复多次请求了。

在项目中最常见的使用方式便是发起请求之前开启Loading和请求响应之后关闭Loading以达到防止重复请求的目的,但是在实际开发中往往会存在以下现象:

  • 发起请求之前开启了Loading,但是请求调用成功之后忘记关闭,导致页面一直处于Loading状态。
  • 发起请求之前忘记开启Loading,导致可能重复点击,影响用户体验。 因此为了避免上述问题的发生,决定将Loading封装在Axios中,以达到目的。

那么如何封装Loading需要我们考虑如下事情:

  • 同一时间内发起多个请求,我们只需要展示一个Loading层即可,不需要产生多个造成重复展示。
  • 同一时间内发起多个请求展示的Loading层以最后一个请求响应而关闭销毁。
  • 此功能依旧要进行可配置化处理。

此处以 ElementPlus 的Loading效果为例进行开发。

此处封装loading共通方法,来实现解耦化,方便后期维护与代码阅读。

loading.ts : 用于定义接口loading,

/**
 * 全局loading效果:合并多次loading请求,避免重复请求
 * 当调用一次showLoading,则次数+1;当次数为0时,则显示loading
 * 当调用一次closeLoading,则次数-1; 当次数为0时,则结束loading
 */
import { ElLoading } from 'element-plus';
 
// 定义一个请求次数的变量,用来记录当前页面总共请求的次数
let loadingRequestCount = 0;
// 初始化loading
let loadingInstance;
 
// 编写一个显示loading的函数 并且记录请求次数 ++
export showLoading = (target) => {
    if (loadingRequestCount === 0) {
        loadingInstance = ElLoading.service({ 
            lock: true, 
            text: '加载中……', 
            background: 'rgba(0, 0, 0, 0.7)'
        });
    }
    loadingRequestCount++
}
 
// 编写一个隐藏loading的函数,并且记录请求次数 --
export closeLoading = () => {
    if (loadingRequestCount <= 0) return
    loadingRequestCount--
    if (loadingRequestCount === 0) {
        loadingInstance.close();
    }
 }

http.ts : 用于axios封装

import axios from 'axios'
import { showLoading, closeLoading } from "@/utils/loading";


axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'

// 创建axios实例

const service = axios.create({

  // axios中请求配置有baseURL选项,表示请求URL公共部分

  baseURL: process.env.VUE_APP_BASE_API,

  // 超时

  timeout: 10000

})
//开启loading
showLoading()
// request拦截器
service.interceptors.request.use(config => {
  if (getToken() && !isToken) {
    config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }
  return config
}, error => {
    console.log(error)
    Promise.reject(error)
})


// 响应拦截器
service.interceptors.response.use(res => {
    //关闭loading,此处采用延时处理是合并loading请求效果,避免多次请求loading关闭又开启
    setTimeout(() => {
        closeLoading()
    }, 200)
  },
  error => {
    setTimeout(() => {
        closeLoading()
    }, 200)
    return Promise.reject(error)
  }
)
export default service

请求失败自动重试

在实际项目中,由于网络是不可靠的,所以经常会有请求失败的场景。针对这种问题,通常的做法是增加重试机制,在请求失败后自动重新发起多次请求,直到达到所设次数,尽量保证请求的成功,从而提高服务的稳定性。

common.ts : 公共函数

// 判断一个字符串是否为JSON字符串
export let isJsonStr = str => {
    if (typeof str == 'string') {
        try {
            var obj = JSON.parse(str);
            if (typeof obj == 'object' && obj) {
                return true;
            } else {
                return false;
            }
        } catch (e) {
            console.log('error:' + str + '!!!' + e);
            return false;
        }
    }
};

againRequest.ts : 用于定义重试请求

import { isJsonStr } from './common';

export function againRequest(err, axios) {
    let config = err.config;
    // config.retry 具体接口配置的重发次数
    if (!config || !config.retry) return Promise.reject(err);

    // 设置用于记录重试计数的变量 默认为0
    config.__retryCount = config.__retryCount || 0;

    // 判断是否超过了重试次数
    if (config.__retryCount >= config.retry) {
        return Promise.reject(err);
    }
    // 重试次数
    config.__retryCount += 1;

    // 延时处理
    var backoff = new Promise(function(resolve) {
        setTimeout(function() {
            resolve();
        }, config.retryDelay || 1000);
    });
    // 重新发起axios请求
    return backoff.then(function() {
        // 判断是否是JSON字符串
        // TODO: 未确认config.data再重发时变为字符串的原因
        if (config.data && isJsonStr(config.data)) {
            config.data = JSON.parse(config.data);
        }
        return axios(config);
    });
}

http.ts : 用于axios封装

import axios from 'axios'
import { againRequest } from '@/utils/requestAgainSend';


axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'

// 创建axios实例

const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API,
  // 超时
  timeout: 10000
})

// request拦截器
service.interceptors.request.use(config => {
    return config
}, error => {
    Promise.reject(error)
})


// 响应拦截器
service.interceptors.response.use(res => {
     return res.data
  },
  error => {
     // 需要特殊处理请求被取消的情况 
     if (!Axios.isCancel(error)) { 
          // 请求重发
          return againRequest(error, axios); 
     }
  }
)
export default service

总结

axios封装没有一个绝对的标准,具体封装结果需要根据项目需求适当变化,希望以上封装案例能够给予您一些帮助。

结语

本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力。