前言

uniapp的请求都是通过uni.request的api实现的,但是这个api和前端常用的axios库用法上有着太多的不同了,移植起来非常痛苦,所以萌生了自己造一个轮子的想法,由于本人技术菜鸡,只能浅浅的仿照一个了。

实际上本人喜欢axios的config的方式调用请求,如下:

axios({
    method: 'post',
    url: '/user/12345',
    data: {
        firstName: 'Fred',
        lastName: 'Flintstone'
    }
});

所以大体上都是围绕这种用法展开工作的。

首先说下使用上的注意事项:

  1. get请求不支持使用data字段,用了估计也会被忽略,get请求请务必使用params字段传参,参考axios的params
  2. create方法创建实例的时候,支持的默认配置只有三个字段:baseURL、timeout、headers
  3. 为了实现promise返回结果,不允许任何调用的请求配置中存在:success、fail、complete回调函数字段,本人通过ts美滋滋的屏蔽了,你用了肯定会报错警告。
  4. 请求前的拦截器只有成功的拦截器,因为axios中的请求前错误拦截器测试根本没有可以触发的地方,所以直接割了这个。
  5. 只有响应后的拦截器允许throw抛出错误和Promise.reject()

基本就这样了,具体可以看看代码,都有注释(优秀作风)

使用

这里是业务层的封装,创建MyAxios实例,配置默认数据baseUrl这些,添加拦截器,请求前对配置对象做特殊处理,请求后解包,报错toast提示。

由于class类的方式,不能将类直接作为函数调用,所以最后提取了api.request方法单独导出了

import MyAxios, { METHODS } from "./myAxios";


/** 第一层数据类型 */
export interface ResponseData {
  code: Number;
  data: Object;
  msg: string;
}

const api = MyAxios.create({
  baseURL: "https://xxx",
  timeout: 1000,
});

/** 请求前拦截器 */
api.useRequestInterceptor((config) => {
  return config;
});

/** 请求后拦截器 */
api.useResponseInterceptor<ResponseData>(
  (res) => {
    //如果code不等于1则说明有错误
    const { code, msg } = res;
    if (code !== 1) {
      uni.showToast({
        title: `网络请求失败`,
        icon: "error",
      });
      return Promise.reject(new Error(msg));
    }
    //解包处理
    const data = res.data;
    return data;
  },
  (error) => {
    uni.showToast({
      title: `网络请求失败`,
      icon: "error",
    });
    return error;
  }
);

/** 提取request请求函数 */
const request = api.request.bind(api);

export { request, METHODS };
export default api;

api层的使用:

import { request, METHODS } from "@/request";

/** 测试接口 */
export function test() {
  return request({
    url: "/test",
    method: METHODS.GET,
    params: {
      name: "我是测试接口",
    },
  });
}

request方法支持泛型,传入一个泛型后就可以推导出本次请求结果的数据类型,方便ts使用了。

import { request, METHODS } from "@/request";

/** 测试接口 */
export function test() {
  return request<string>({  //这里的string就是响应拦截器中res.data解包之后的值了
    url: "/test",
    method: METHODS.GET,
    params: {
      name: "我是测试接口",
    },
  });
}

具体的业务使用:

import { test } from "@/apis/test";

test()
    .then((res) => {
      console.log(res);
    })
    .catch((error) => {
      console.log(error);
    });

MyAxios封装


/** 请求方法常量 */
export enum METHODS {
  "OPTIONS" = "OPTIONS",
  "GET" = "GET",
  "HEAD" = "HEAD",
  "POST" = "POST",
  "PUT" = "PUT",
  "DELETE" = "DELETE",
  "TRACE" = "TRACE",
  "CONNECT" = "CONNECT",
}

/** 头信息 */
export interface Headers {
  [key: string]: string | number | boolean | undefined | null;
}

/** 实例化的配置 */
export interface AxiosConfig {
  baseURL?: string;
  timeout?: number;
  headers?: Headers;
}

/** 默认配置 */
const defaultConfig: AxiosConfig = {
  baseURL: "",
  timeout: 60000,
  headers: {},
};

/** 定制的请求参数 */
export interface RequestOptions extends Omit<UniNamespace.RequestOptions, "success" | "fail" | "complete"> {}

/** 已知请求类型的配置 */
export interface KnownConfig extends Omit<RequestOptions, "method"> {}

/** get请求类型配置 */
export interface GetConfig extends Omit<KnownConfig, "data"> {
  params?: Record<string, any>;
}

/** post请求类型配置 */
export interface PostConfig extends KnownConfig {
  params?: Record<string, any>;
}

/** request请求类型配置 */
export interface RequestConfig extends RequestOptions {
  params?: Record<string, any>;
}

/** 错误对象 */
export interface AxiosError extends Error {
  options?: RequestOptions;
}

/** 请求前拦截器 */
export type RequestInterceptorFn = (config: RequestOptions) => RequestOptions;
export type RequestInterceptorArray = Array<RequestInterceptorFn>;
/** 请求成功拦截器 */
export type ResponseSuccessInterceptorFn<T = any> = (result: T) => any;
export type ResponseSuccessInterceptorArray<T> = Array<ResponseSuccessInterceptorFn<T>>;
/** 请求失败拦截器 */
export type ResponseInterceptorFailFn = (error: AxiosError | Error) => AxiosError | Error;
export type ResponseInterceptorFailArray = Array<ResponseInterceptorFailFn>;

class Axios {
  /** createOptions */
  private createOptions: AxiosConfig;
  /** 取消请求函数存储器 */
  private cancelTokenMap: Map<string, Function> = new Map();
  /** 请求前拦截器 */
  private requestInterceptors: RequestInterceptorArray = [];
  /** 请求后成功的拦截器 */
  private responseSuccessInterceptors: ResponseSuccessInterceptorArray<any> = [];
  /** 请求后失败的拦截器 */
  private responseFailInterceptors: ResponseInterceptorFailArray = [];

  constructor(options: AxiosConfig = defaultConfig) {
    const { baseURL, timeout, headers } = options;
    this.createOptions = {
      baseURL: baseURL ?? defaultConfig.baseURL,
      timeout: timeout ?? defaultConfig.timeout,
      headers: headers ?? defaultConfig.headers,
    };
  }

  /** 静态创建实例方法 */
  public static create(options?: AxiosConfig): Axios {
    return new Axios(options);
  }

  /** get请求 */
  public get<T = any>(options: GetConfig): Promise<T> {
    const getOptions: KnownConfig = { ...options };
    if (options.params) {
      getOptions.data = options.params;
      Reflect.deleteProperty(getOptions, "params");
    }
    const requestConfig = this.mergeDefaultConfig(getOptions);
    return this._request<T>({ method: METHODS.GET, ...requestConfig });
  }

  /** post请求 */
  public post<T = any>(options: PostConfig): Promise<T> {
    let postOptions: KnownConfig = { ...options };
    //生成参数字符串
    const params = options.params;
    let paramsStr = "";
    if (params) {
      Object.keys(params)
        .sort((a, b) => a.localeCompare(b))
        .forEach((key) => {
          paramsStr += `${key}=${params[key]}&`;
        });
      paramsStr = paramsStr.replace(/&$/, "");
    }
    //拼接参数字符串
    if (postOptions.url.includes("?")) {
      postOptions.url += `&${paramsStr}`;
    } else {
      postOptions.url += `?${paramsStr}`;
    }
    const requestConfig = this.mergeDefaultConfig(postOptions);
    return this._request<T>({ method: METHODS.POST, ...requestConfig });
  }

  /** 自动判断get和post请求的请求方法 */
  public request<T = any>(options: RequestConfig): Promise<T> {
    let { method } = options;
    if (!method) method = METHODS.GET;

    switch (method) {
      case METHODS.GET:
        return this.get<T>(options);
      case METHODS.POST:
        return this.post<T>(options);
      default:
        return Promise.reject(new Error("不支持的请求方法"));
    }
  }

  /** 获取uni的请求并类型转换为Promise */
  private _request<T = any>(options: RequestConfig): Promise<T> {
    return new Promise<T>((resolve, reject) => {
      //请求前拦截器
      let requestConfig = options;
      this.requestInterceptors.forEach((interceptor) => {
        requestConfig = interceptor(requestConfig);
      });
      const requestTask = uni.request({
        ...requestConfig,
        success: async (res) => {
          try {
            const { statusCode } = res;
            //错误处理
            if (statusCode !== 200) {
              let msg = "请求失败";
              const data = res.data as { msg: string };
              if (typeof data.msg === "string") msg = data.msg;
              let error: AxiosError = this.createError(msg, options);
              //删除取消函数
              this.cancelTokenMap.delete(options.url);
              //请求失败拦截器
              for (let i = 0, len = this.responseFailInterceptors.length; i < len; i++) {
                error = this.responseFailInterceptors[i](error);
              }
              return reject(error);
            }
            //删除取消函数
            this.cancelTokenMap.delete(options.url);
            //请求成功拦截器
            let resultData = res.data;
            for (let i = 0, len = this.responseSuccessInterceptors.length; i < len; i++) {
              resultData = await this.responseSuccessInterceptors[i](resultData);
            }
            return resolve(resultData as T);
          } catch (err: any) {
            let msg = "请求失败";
            if (typeof err.errMsg === "string") msg = err.errMsg;
            const error: AxiosError = this.createError(msg, options);
            //删除取消函数
            this.cancelTokenMap.delete(options.url);
            //请求失败拦截器
            for (let i = 0, len = this.responseFailInterceptors.length; i < len; i++) {
              err = this.responseFailInterceptors[i](err);
            }
            return reject(err);
          }
        },
        fail: (err) => {
          let msg = "请求失败";
          if (typeof err.errMsg === "string") msg = err.errMsg;
          let error: AxiosError = this.createError(msg, options);
          //删除取消函数
          this.cancelTokenMap.delete(options.url);
          //请求失败拦截器
          for (let i = 0, len = this.responseFailInterceptors.length; i < len; i++) {
            error = this.responseFailInterceptors[i](error);
          }
          return reject(error);
        },
      });
      //存储取消函数
      this.cancelTokenMap.set(options.url, requestTask.abort.bind(requestTask));
    });
  }

  /** 获取用户配置和默认配置的合并后的配置对象 */
  private mergeDefaultConfig(options: KnownConfig): RequestOptions {
    const { baseURL, timeout, headers } = this.createOptions;
    const url = `${baseURL}${options.url ?? ""}`;
    const mergeOptions = { timeout, ...options };
    Reflect.deleteProperty(mergeOptions, "url");
    mergeOptions.url = url;
    const oh = options.header;

    if (oh) {
      mergeOptions.header = { ...headers, ...oh };
    } else {
      mergeOptions.header = headers || {};
    }

    return mergeOptions;
  }

  /** 创建错误对象 */
  private createError(msg: string, options: RequestOptions): AxiosError {
    const error: AxiosError = new Error(msg);
    error.options = options;
    return error;
  }

  /** 通过url获取取消请求函数 */
  public getRequestAbort(url: string): Function | undefined {
    return this.cancelTokenMap.get(url);
  }

  /** 注册请求前的拦截器 */
  public useRequestInterceptor(fn: RequestInterceptorFn): void {
    this.requestInterceptors.push(fn);
  }

  /** 注册请求后的拦截器 */
  public useResponseInterceptor<T>(successFn: ResponseSuccessInterceptorFn<T>, failFn?: ResponseInterceptorFailFn): void {
    this.responseSuccessInterceptors.push(successFn);
    failFn && this.responseFailInterceptors.push(failFn);
  }
}

export default Axios;

取消请求

其实我把取消请求功能也加上了,通过实例api.getRequestAbort(url)方法,但是个人感觉不是很好使,这个url是一个完整链接哦,是包含baseUrl和链接参数的,因为实在想不到啥好办法去区分了。

如果你需要的话,可以自行使用,getRequestAbort得到的就是abort取消请求函数,运行它即可。

需要注意的是,如果这个请求已经完成了,success或者fail,那个getRequestAbort就获取不到了,因为我把abort清理了。

分类: UNI-APP 标签: axios拦截器取消请求uni.request

评论

全部评论 1

  1. a
    a
    Google Chrome Windows 10
    试一下能不能发

目录