axios响应拦截处理(二次封装)搭配await更适合

axios请求拦截:添加额外参数:

平时接口封装:

import request from '@/utils/request';

// 
export const getList = async (data, otherConfig) => {
  return await request({
    url: `getList`,
    method: 'post',
    data,
    otherConfig
  });
};

// 平时使用,特殊页面
const res = await getList(data,{loading:true,...})


场景1:如果确定该接口为长接口,比较耗时等,其他相关配置baseURL等
export const getList = async (data, otherConfig) => {
  return await request({
    url: `getList`,
    method: 'post',
    data,
    otherConfig:{
      ...otherConfig,
      timeout:60000,
      baseURL:'/api'
    }
  });
};

// response 拦截器
service.interceptors.response.use(
  (response) => {
    const res = response.data;
    // 其他处理省略...
    return res;
  },
  async (error) => {
    // 根据实际情况设置
    // 如果是返回reject则会被catch捕获,await之后的代码不会执行
    // return Promise.reject(error);
    //  如果是以下,则不会被catch捕获,await后代码会执行
    return Promise.resolve(error)
  
  }
);

普通情况:接口error错误时返回 Promise.reject(err) 不再推荐

接口响应拦截处理
错误时返回 return Promise.reject(error) ===  中断拦截

const funA = async () => {
  try{
    const res = await 异常接口()
    console.log('AAA:',res);
  }catch(err){
    console.log('AAA:err',err);  
  }

}

const funB = async () => {
  try{
    const res = await 正常接口()
    console.log('BBB:',res);
  }catch(err){
    console.log('BBB:err',err);  
  }
}



需求一:需要等A执行完再执行B
// 生命周期调用
onMounted(async () => {
  await funA();
  await funB();
});

场景1:代码习惯使用  try{} catch(err){}  捕获
// 执行结果
!被catch捕获,打印 AAA:err 跟 BBB



场景2:代码习惯不使用  try{} catch(err){}  捕获
// 执行结果
直接报错,什么都不打印,==>>> funB会被funA拦截,也就是说A接口异常都会影响其他的

场景3:funA、funB 都正常,但是A接口比较慢,使用  try catch 捕获
// 执行结果
打印顺序正常

场景4:funA、funB 都正常,但是A接口比较慢,使用  try catch 捕获,生命周期不使用 await
onMounted(async () => {
   funA();
   funB();
}); 
// 执行结果
先打印BBB,再打印AAA,无法做到异步效果

综上所述:
想要防止一个接口异常影响其他接口,需要每个接口都要用 try catch 去捕获异常并写逻辑,或者使用 await 接口().catch(err=>{ ... }) 
需要做到异步效果,也是等一个接口处理完再去处理其他逻辑,则需要在生命周期使用 async await


特殊情况:错误时返回 Promise.resolve(err) 推荐写法

接口响应拦截处理
错误时返回 return Promise.resolve(error) === 放行

const funA = async () => {
  try{
    const res = await 异常接口()
    console.log('AAA:',res);
  }catch(err){
    console.log('AAA:err',err);  
  }

}

const funB = async () => {
  try{
    const res = await 正常接口()
    console.log('BBB:',res);
  }catch(err){
    console.log('BBB:err',err);  
  }
}



需求一:需要等A执行完再执行B
// 生命周期调用
onMounted(async () => {
  await funA();
  await funB();
});

场景1:代码习惯使用  try{} catch(err){}  捕获
// 执行结果
打印 AAA 跟 BBB ===>>> 无法被 catch 捕获! 无论正常异常都会进入 try

  
场景2:代码习惯不使用  try{} catch(err){}  捕获
// 执行结果
打印 AAA 跟 BBB ===>>> 打印顺序正常! 


场景3:funA、funB 都正常, 生命周期使用 await ,但是A接口比较慢,
// 执行结果
先后打印顺序正常,异步正常

场景4:funA、funB 都正常,但是A接口比较慢,生命周期不使用 await
onMounted(async () => {
   funA();
   funB();
}); 
// 执行结果
先打印BBB,再打印AAA,无法做到异步效果


综上所述,如果想要捕获接口异常并做出处理,只能通过 res.code === 0/200 (根据后端状态码)  去进行判断,或者只做成功判断(日常接口使用)
要达到 异步效果则需要在生命周期/方法 使用 async await 

最终接口axios封装文件

import axios from 'axios';
import { getToken } from '@/utils/token';
import { ElNotification, ElMessageBox, ElMessage, ElLoading } from 'element-plus';
import router from '@/router';
import { useUserStore } from '@/stores/user';

/**
 * @description: request作用域内的全局变量和函数
 * @param TIMEOUT 请求超时时间 ms
 * @param TimeoutMsg 请求超时时间 提示
 * @param reqWithLoadingNum 开启loading的请求数
 * @param duration 保证toast显示3s 才被关闭  避免提示过短
 * @param timeoutTimer 超时提醒定时器
 * @param loadingInstance loading实例
 */

const TIMEOUT = 30000;
const TimeoutMsg = '网络异常,请求超时';
let reqWithLoadingNum = 0;
let duration = 3000;
let timeoutTimer = null;
let loadingInstance = null;

/**
 * @description: 开启loading
 */
const showLoading = (params) => {
  // 同一个页面仅第一次loading展示 避免页面多次出现loading
  if (reqWithLoadingNum <= 0) {
    // showloading
    loadingInstance = ElLoading.service({
      fullscreen: true,
      lock: true,
      text: params || '接口请求中~',
      background: 'rgba(0, 0, 0, 0.7)'
    });

    // 做超时处理
    timeoutTimer && clearTimeout(timeoutTimer);
    timeoutTimer = setTimeout(() => {
      loadingInstance.close();
      clearTimeout(timeoutTimer);
      if (reqWithLoadingNum > 0) {
        // 存在请求超时
        ElMessage({
          type: 'error',
          message: TimeoutMsg,
          duration
        });
      }
    }, TIMEOUT + 1000);
  }
  reqWithLoadingNum += 1;
};

/**
 * @description: 关闭loading
 */
const hideLoading = (force = false) => {
  reqWithLoadingNum -= 1;
  if (reqWithLoadingNum <= 0 || force) {
    reqWithLoadingNum = 0;
    loadingInstance.close();
  }
};

const service = axios.create({
  // api 的 base_url
  baseURL: import.meta.env.VITE_APP_BASE_API,
  timeout: 30000
});

/**
 * @description: request拦截器
 * @param extendParam <Object> 自定义config,自定义timeout、loading
 * 特殊接口timeout需要加长
 */
service.interceptors.request.use(
  (config) => {
    if (config?.otherConfig) {
      config = {
        ...config,
        ...config.otherConfig
      };
    }
   
    if (getToken()) {
      // 让每个请求携带自定义token 请根据实际情况自行修改
      config.headers['x-auth-token'] = getToken();
    }

    // 全局AXIOS请求加载
    if (config?.otherConfig?.loading === true) {
      showLoading();
    }

    return config;
  },
  (err) => {
    Promise.reject(err);
  }
);

// response 拦截器
service.interceptors.response.use(
  (response) => {
    if (response?.config?.otherConfig?.loading === true) {
      hideLoading();
    }

    const res = response.data;

    if (res.code) {
      // 不能在头部引入,调用顺序问题,pinia还未被创建
      const userStore = useUserStore();
      switch (res.code) {
        case 70403:
        case 71402:
        case 401:
          ElMessageBox.alert('登录状态已过期,请您重新登录!', '系统提示', {
            confirmButtonText: '重新登录',
            type: 'warning'
          }).then(() => {
            userStore.logout().then(() => {
              router.push('/login');
            });
          });

          break;
        case 70401:
        case 403:
          ElNotification.error({
            title: '错误',
            message: res.message || '您无权进行此操作,请求执行已拒绝'
          });

          break;
        case 0:
        case 200:
          break;

        case 201:
        case 202:
        case 203:
          ElNotification.success({
            title: '成功',
            message: res.message
          });

          break;

        default:
          ElNotification.error({
            title: '错误',
            message: res.message || '请求失败'
          });

          break;
      }
    }

    return res;
  },
  async (err) => {
    
    if (err?.config?.otherConfig?.loading === true) {
      hideLoading();
    }
    ElNotification.error({
      title: '错误',
      message: err?.message || '请求失败'
    });

    // 根据实际情况设置
    // 如果是返回reject则会被catch捕获,await之后的代码不会执行
    // return Promise.reject(error);

    //  如果是以下两种,则不会被catch捕获,await后代码会执行
    return Promise.resolve(err);
    // return error
  }
);
export default service;

后续会不断更新

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值