Vue项目中基于面对对象式的axios的二次封装

542 阅读3分钟

背景

最近在和友商合作开发一个企微的应用,可能需要用到两种接口

  • 内部接口:需要token 和 authorization 以及其他请求头字段等。
  • 外部接口:直接调用即可

思路回顾

主要是利用了ES6的 class 关键字定义一个请求类,在类上定义一个静态方法(static),静态方法实例无法调用,类可以直接调用;类上还定义了各种各样的请求(post、postJson、get、put、putJson、delete)等,针对每一个请求单独设置其配置,最终通过axios.create([config]).then((result)=>{}),取到数据等。

文件目录层级


base.js
Request.js
LSRequest.js

实现

base.js

//抽离出来方便修改
export OPEN_URL = "xxx.xxx.xxx";//外部base
export INNER_URL = "xxx.xxx.xxx";//内部base

Request.js(外部接口调用)

import axios from "axios";
// Axios 插件 重试失败的请求
import axiosRetry from "axios-retry";
// qs 是一个增加了一些安全性的查询字符串解析和序列化字符串的库。
import qs from "qs";
// 提示窗口应用vant的Toast组件
import {Toast} from 'vant';
axiosRetry(axios, {
  retries: 3,
  retryCondition: () => true,
  retryDelay: () => 1000
});
let toastCount = 0;
//请求拦截器
axios.interceptors.request.use(
  config => {
     if (!config._asyn) {
       Toast.loading({
        message: '加载中...',
         forbidClick: true,
         duration: 0,
       });
      toastCount++;
     }
    return config;
  },
  err => {
    console.log(err);
    return Promise.reject(err);
  }
);
// 响应拦截器
axios.interceptors.response.use(
  response => {
    if (!response.config._asyn) {
      toastCount--;
      if (toastCount <= 0) {
        Toast.clear();
      }
    }
    return response;
  },
  err => {
    toastCount--;
    if (toastCount <= 0) {
      Toast.clear();
    }
    return Promise.reject(err);
  }
);
//通过class关键字定义一个Request类
export default class Request {

 //构造方法,而this关键字则代表实例对象。
  constructor(url) {
    this.requestConfig = {url: url, headers: {}};
  }
  
  //类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
  如果在一个方法前,加上 `static`关键字,就表示该方法不会被实例继承,
  而是直接通过类来调用,这就称为“静态方法”。
  static create(url) {
    return new Request(url);
  }

  asyn() {
    this.requestConfig._asyn = true;
    return this;
  }

  header(key, value) {
    this.requestConfig.headers[key] = value;
    return this;
  }

  post(param) {
    this.requestConfig.method = 'post';
    this.requestConfig.data = qs.stringify(param, {
       skipNulls: true,//skipNulls选项可以忽略Null值的解析:
       encodeValuesOnly: true,//通过设置 encodeValuesOnly 为 true,可以禁用对 key 进行URI 编码:
       indices: false,//indices 为 false 不显示索引:
    });
    this.header("Content-Type", "application/x-www-form-urlencoded");
    return this.execute();
  }

  postJson(param) {
    this.requestConfig.method = 'post';
    this.requestConfig.data = param;
    this.header("Content-Type", "application/json");
    return this.execute();
  }

  get(param) {
    this.requestConfig.method = 'get';
    this.requestConfig.params = param;
    this.requestConfig.paramsSerializer = params =>
      qs.stringify(params, {
        skipNulls: true,
        encodeValuesOnly: true,
        indices: false,
      });
    this.header("Content-Type", "application/x-www-form-urlencoded");
    return this.execute();
  }

  /**
   * put调用  application/x-www-form-urlencoded
   * @param param 参数
   * @returns {Promise<unknown>}
   */
  put(param) {
    this.requestConfig.method = 'put';
    this.requestConfig.data = qs.stringify(param, {
      skipNulls: true,
      encodeValuesOnly: true,
      indices: false
    });
    this.header("Content-Type", "application/x-www-form-urlencoded");
    return this.execute();
  }

  /**
   * put调用  application/json
   * @param param 参数
   * @returns {Promise<unknown>}
   */
  putJson(param) {
    this.requestConfig.method = 'put';
    this.requestConfig.data = param;
    this.header("Content-Type", "application/json");
    return this.execute();
  }

  /**
   * delete调用  application/x-www-form-urlencoded
   * @param param 参数
   * @returns {Promise<unknown>}
   */
  delete(param) {
    this.requestConfig.method = 'delete';
    this.requestConfig.params = param;
    this.requestConfig.paramsSerializer = params =>
      qs.stringify(params, {
        skipNulls: true,
        encodeValuesOnly: true,
        indices: false,
      });
    this.header("Content-Type", "application/x-www-form-urlencoded");
    return this.execute();
  }


  execute() {
    //使用自定义配置新建一个 axios 实例
    return axios.request(this.requestConfig).then(response => {
      return Promise.resolve(response.data);
    });
  }
}

LSRequest.js(内部调用)

import Request from "./Request";
import {Toast} from 'vant';
import { INNER_URL } from "./base";//定义在base.js 里面的请求路径
//是否正在刷新标记
let isRefresh = false;
// 重试队列,每一项将是一个待执行的函数形式
let requests = [];

function flushToken(action) {
  // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
  let p = new Promise(resolve => {
    requests.push(() => {
      resolve(action());
    });
  });
  if (!isRefresh) {
    isRefresh = true;
    LsRequest.create("userauth/token/refresh").post({
      authorization: sessionStorage.getItem("authorization"),
      refresh_token: sessionStorage.getItem("refreshToken")
    }).then(token => {
      sessionStorage.setItem("authorization", token.principal);
      sessionStorage.setItem("token", token.token);
      sessionStorage.setItem("refreshToken", token.refreshToken);
      requests.forEach(callback => callback());
      requests = [];
    });
  }
  return p;
}

const baseUrl = INNER_URL;
export default class LsRequest extends Request {
  constructor(url) {
    if (!url.startsWith("/")) {
      url = "/" + url;
    }
    //super键字只能够以指定的形式出现在以下地点,用在子类的constructor函数中;
    关键字用于访问父对象上的函数,
    super(baseUrl + url);
  }

  static create(url) {
    return new LsRequest(url);
  }

  execute() {
    //设置一些请求头
    this.header("authorization", sessionStorage.getItem("authorization"));
    this.header("token", sessionStorage.getItem("token"));
    //父类方法是可以从`super`对象上调用的
    return super.execute().then(response => {
      let data = response.data;
      let code = response.code;
      let message = response.message;
      switch (code) {
        case 200:
          return Promise.resolve(data);
        //4012 标明token过期,重新刷新token
        case 4012:
          return flushToken(this.execute);
        case 406:
          Toast.fail(message);
          break;
        case 4011:
          //清除缓存数据,重定向到首页
          sessionStorage.clear();
          router.replace({
             path: "/"
           });
          break;
        case 400:
          Toast.fail("参数错误:" + message);
          break;
        case 401:
          Toast.fail("权限错误");
          break;
        case 404:
          Toast.fail("04 NOT FOUND");
          break;
        case 500:
          Toast.fail("网络开小差了");
          break;
        default:
          break;
      }
      return Promise.reject({code: code, message: message});
    });
  }
}

在mian.js 中将上述导出的方法挂载到全局

Vue.prototype.nativeRequest = Request.create;
Vue.prototype.request = LsRequest.create;

在需要的地方调用即可:

//内部调用
  getNoticeList() {
        this.request("case-field/message/userMessage")
          .get({})
          .then(result => {...});
      },
// 外部调用
 getAudioList(){
 this.nativeRequest( this.ML_URL+'/leSoft/audio/' + id)
            .get({ })
            .then((r) => {...});
      },

ps :上班的时候偷偷写的,有点慌