鸿蒙开发--基于axios封装网络请求实现链式调用

86 阅读4分钟

比较喜欢链式调用的方式,需要设置什么参数,调用具体的方法,视觉和逻辑上简洁明了。实现一个基于axios的网络请求封装,实现链式调用的效果

1、使用方式

  //调用方式
  HttpHelper.newClient<DataBean>()
      .host("https://www.wanandroid.com")//可以全局定义域名,也可以后续传入,可用于多域名的情况
      .api("article/list/0/json")//接口路径
      .enableShowLoading(true)//是否显示加载弹窗
      .enableShowToast(true)//是否toast显示请求错误信息
      .params(Object({ "cid": 60 }))//传入参数
      .onSuccess((info) => {
      //成功回调,如果不需要可以不写
        data(info)
      })
      .onError((error) => {
      //错误回调,如果不需要可以不写
      })
      .get()//.post() .delete() put()
     
    //使用中一般只需要这样,
class TestViewModel {
  getListData2(data: (data?: DataBean) => void) {
    HttpHelper.newClient<DataBean>()
      .api("article/list/0/json")
      .params(Object({ "cid": 60 }))
      .onSuccess((info) => {
        data(info)
      })
      .get()////.post() .delete() put()
  }
}
​

2、实现方法

定义后端返回的数据格式,按需定义

//服务端返回的数据格式
export interface  BaseApiResponse<T> {
  errorCode?: number|string
  errorMsg?: string
  data?: T
}
​
//封装一个错误信息对象
export interface BaseError {
  message: string;
  code?: number | string;
}

实现类

/**
 * 链式调用实现网络请求
 */
export class HttpHelper<T> {
  private baseUrl: string = "https://www.wanandroid.com" //请求域名,默认放置
  private timeOut: number = 10000 //超时时间
​
  private requestApi?: string //接口路径
  private paramsData?: object //参数
​
​
  private isShowLoadingDialog: boolean = true//是否显示加载中弹窗
  private isShowErrorToast: boolean = true//是否自动显示错误信息
  private axiosInstance?: AxiosInstance; // axios 实例
​
​
  private success?: (result?: T) => void;//请求成功返回正确数据的回调
  private error?: (result?: BaseError) => void;//请求失败的回调
​
  //链式调用,禁掉手动new对象
  private constructor() {
  }
​
​
  // axios 请求配置
  private defaultConfig: AxiosRequestConfig = {
    baseURL: this.baseUrl,
    timeout: this.timeOut
  }
​
  /**
   * 动态设置域名
   * @param host
   * @returns
   */
  host(host: string): HttpHelper<T> {
    this.baseUrl = host
    return this;
  }
​
​
  /**
   * 接口路径
   * @param api
   * @returns
   */
  api(api?: string): HttpHelper<T> {
    this.requestApi = api
    return this
  }
​
/**
 * 请求参数
 * @param params
 * @returns
 */
  params(params?: object): HttpHelper<T> {
    this.paramsData = params
    return this
  }
​
​
/**
 * 创建对象
 * @returns
 */
  public static newClient<T>(): HttpHelper<T> {
    return new HttpHelper()
  }
​
  /**
   * 外部传入axios 配置,好像用不到,后续如果需要配置,仍是使用封装类增加相关配置
   * @param config
   * @returns
   */
  // config(config: AxiosRequestConfig): HttpHelper<T> {
  //   this.defaultConfig = config
  //   return this
  // }
​
​
  /**
   * 使用get方法
   */
  get() {
    this.request(RequestType.GET)
  }
  /**
   * 使用post方法
   */
  post() {
    this.request(RequestType.POST)
  }
  /**
   * 使用put方法
   */
  put() {
    this.request(RequestType.PUT)
  }
  /**
   * 使用delete方法
   */
  delete() {
    this.request(RequestType.DELETE)
  }
​
​
  /**
   * 传入成功回调函数
   * @param success
   * @returns
   */
  onSuccess(success?: (result?: T) => void): HttpHelper<T> {
    this.success = success;
    return this;
  }
​
  /**
   * 传入请求错误回调
   * @param error
   * @returns
   */
  onError(error?: (result?: BaseError) => void): HttpHelper<T> {
    this.error = error;
    return this;
  }
​
  /**
   * 设置是否允许显示加载中弹窗
   * @param enableLoading
   * @returns
   */
  enableShowLoading(enableLoading: boolean): HttpHelper<T> {
    this.isShowLoadingDialog = enableLoading;
    return this;
  }
  /**
   * 设置是否允许显示错误信息toast
   * @param enableLoading
   * @returns
   */
  enableShowToast(enable: boolean): HttpHelper<T> {
    this.isShowErrorToast = enable
    return this
  }
​
​
  /**
   * 在发起请求方法前,更新下配置信息
   */
  private updateBaseConfig() {
    this.defaultConfig.baseURL = this.baseUrl
    this.defaultConfig.timeout = this.timeOut
​
    this.axiosInstance = axios.create(this.defaultConfig);
    this.initIntercept()
  }
​
​
  /**
   * 设置拦截器信息,需要添加token,可以在这边全局设置
   */
  private initIntercept() {
    // 添加请求拦截器
    this.axiosInstance?.interceptors.request.use((config: InternalAxiosRequestConfig) => {
      let info = `请求路径 ${config.baseURL}${config.url}\n请求参数 ${JSON.stringify(config.params)}`
      LogUtil.info(info)
      return config;
    }, (error: AxiosError) => {
      return Promise.reject(error);
    });
  }
​
  
  private showLoading() {
    if (this.isShowLoadingDialog) {
      LoadingDialog.getInstance().show()
    }
  }
​
  private hideLoading() {
    if (this.isShowLoadingDialog) {
      LoadingDialog.getInstance().hide()
    }
  }
​
  private showToast(message?: string) {
    if (this.isShowErrorToast && message) {
      ToastUtil.showShort(message)
    }
  }
​
​
  /**
   * 请求接口具体实现
   * @param type
   */
  private request(type: RequestType) {
​
    this.updateBaseConfig()
    this.showLoading()
    let response: Promise<BaseResponse<BaseApiResponse<T>>>
​
    switch (type) {
      case RequestType.GET:
        response = this.axiosInstance!.get<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
          { params: this.paramsData })
        break
      case RequestType.POST:
        response = this.axiosInstance!.post<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
          { params: this.paramsData })
        break
      case RequestType.PUT:
        response = this.axiosInstance!.put<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
          { params: this.paramsData })
        break
      case RequestType.DELETE:
        response = this.axiosInstance!.delete<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
          { params: this.paramsData })
        break
​
      default:
        response = this.axiosInstance!.get<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
          { params: this.paramsData })
        break
    }
​
    response.then((res) => {
      let data = res.data
      //获取到的数据无法像java那样直接映射成具体的数据对象类型,就算强转类型也无法使用对象中的方法
      if (data.errorCode == 0) {
        //请求成功获取到预期数据
        if (this.success) {
          this.success(data.data)
        }
      } else {
        //请求成功,但是是返回服务器错误信息
        if (this.error) {
          let errBean: BaseError = {
            code: data.errorCode,
            message: data.errorMsg ?? ""
          }
          this.showToast(errBean.message)
          if (this.error) {
            this.error(errBean)
          }
        }
      }
    }).catch((error: AxiosError) => {
      //请求失败,如网络无法连接
      let errBean: BaseError = {
        code: error.code,
        message: error.message
      }
​
      this.showToast(errBean.message)
​
      if (this.error) {
        this.error(errBean)
      }
    }).finally(() => {
      this.hideLoading()
​
    });
  }
}

3、问题

当后端返回的数据类型不统一时可能无法正常使用,比如:

{
code:1
message:""
data:{...}
}
​
{
errorCode:1
errorMsg:""
datas:{...}
}

封装实现了分割请求成功和失败的两种情况,避免了各个接口请求的公共逻辑代码(如弹窗、错误信息显示),对于拿到手的数据可以更专注于请求成功后的逻辑处理,但是这样请求的数据格式在框架中就限制死了

    let response: Promise<BaseResponse<BaseApiResponse<T>>>

如果拿到了不是这个格式的数据,那么后续的逻辑处理都可能出现问题。

尝试过把BaseApiResponse整个当做HttpHelper的泛型传入,打算通过封装BaseApiResponse的子类来解决格式不统一的问题,但是,由于语言限制,好像鸿蒙中无法像android中json和对象互相映射的功能,android中可以创建多种数据格式的子类,利用多态特性,关系映射解决掉这个问题,但是鸿蒙好像还不行

华为官方关于Json解析成对象的相关说明 ,使用第三方映射库还是得传入实际的对象类型,暂时是想不到怎么在底层拿到上层的数据类型。

暂时没有比较完美的解决方式,本地端能处理的估计就是在拿到数据后如果已知数据格式不统一,在拦截器中修改成需要的数据类型。

不知道有没有大佬看到,希望不吝赐教。