uni-app 怎么实现http请求拦截

3,825 阅读6分钟

前言

随着业务的需求,项目需要支持H5、各类小程序以及IOS和Android,这就需要涉及到跨端技术,不然每一端都开发一套,人力成本和维护成本太高了。团队的技术栈主要以Vue为主,最终的选型是以uni-app+uView2.0作为跨端技术栈。以前一直听别人吐槽uni-app怎么怎么不好,但是没什么概念,这一次需要为团队开发一个项目的基础框架和一些示例页面,主要是支持路由拦截http请求多实例请求数据加密以及登录功能封装,发现uni-app的生态不怎么健全,比如我们项目很需要的路由拦截,http请求拦截,这些都没有提供,对于跨端的兼容问题也挺多的。这篇文章聊聊http请求封装的调研,以及最终的选择和实现。

实现http请求的方式

  • 使用原生uni.request
  • uni.request进行封装
  • 使用luch-request插件

使用原生uni.request

对于直接使用uni.request进行接口请求,应该是较少人的选择吧。这个跟我们现在较少的直接使用Ajax的原因是一致,多个请求嵌套,又进入回调地狱的圈套,这是我们不愿看到的以会对其做一层Promise的封装。原生uni.request缺少接口请求拦截,这也是我们必不可少的一环。

uni.request进行封装

我们已经了解原生uni.request的硬伤,1、不支持Promise从而导致容易引发回调地狱。2、不支持请求拦截和响应拦截,从而导致使用的诸多不便。发现问题,就应当解决问题,所以我们可以对uni.request做进一步的封装,使其支持我们想要的功能。

request封装

// request.js

// lodash 合并函数,也可以自己实现
import merge from "lodash.merge";

// 默认配置
const DEFAULT_CONFIG = {
  baseUrl: "http://xxx.api",
  data: {},
  header: {},
  method: "post",
  timeout: 150000,
  dataType: "json",
  responseType: "text",
  sslVerify: true,
  withCredentials: false,
  firstIpv4: false,
};
class Request {
  constructor(options = {}) {
    // 合并用户自定义配置
    this.config = merge({}, DEFAULT_CONFIG, options);
  }
  // 请求拦截 主要是合并url,合并接口特定配置,可以根据自己情况进行扩展
  requestInterceptor(url, data, config, method) {
    const { baseUrl } = this.config;
    // 拼接Url
    url = baseUrl + url;
    const configs = {
      ...this.config,
      url,
      data,
      ...config,
      method,
    };
    // 返回组装的配置
    return configs;
  }
  // 响应拦截,这里只是做了示例,可以根据自己情况进行扩展
  async responseInterceptor(res) {
    const { data: _data } = res;
    const { code, message, data } = _data;
    if (code !== "200") {
      this.handleError(message);
      return Promise.reject(message);
    }
    return data;
  }
  // 请求方法,做了Promise封装,返回Promise
  /**
   * @param {String} url 接口
   * @param {Object} data 参数
   * @param {Object} config 某个接口自定义配置
   * @param {String} method 请求方法,只实现post和get,这么做了原因是 只有这两个没有兼容问题
   * @returns
   */
  request(url, data, config, method) {
    // 显示loading
    uni.showLoading();
    // 请求拦截,返回处理过的结果配置
    const _config = this.requestInterceptor(url, data, config, method);
    // Promise 封装
    return new Promise((resolve, reject) => {
      uni.request({
        ..._config,
        success: (res) => {
          // 这种简写方式一时看不懂,可以替换为注释的写法
          this.responseInterceptor(res).then(resolve).catch(reject);
          // this.responseInterceptor(res).then(res => {
          //   resolve(res)
          // }).catch(err => {
          //   reject(err)
          // });
        },
        fail: (err) => {
          // 提示错误
          this.handleError(err.message);
          console.log("fail", err);
        },
        complete: () => {
          // 关闭Loading
          uni.hideLoading();
        },
      });
    });
  }
  // 只实现post和get,这么做了原因是 只有这两个没有兼容问题
  // 需要其他方式,可以以同样的方式自行扩展

  /**
   * get请求
   * @param {String} url 接口
   * @param {Object} data 请求参数 可选
   * @param {Object} config 接口自定义配置 可选
   * @returns
   */
  get(url, data = {}, config = {}) {
    return this.request(url, data, config, "GET");
  }
  /**
   * post请求
   * @param {String} url 接口
   * @param {Object} data 请求参数 可选
   * @param {Object} config 接口自定义配置 可选
   * @returns
   */
  post(url, data = {}, config = {}) {
    return this.request(url, data, config, "POST");
  }
  // 错误提示
  handleError(title) {
    uni.showToast({
      title,
      icon: "none",
    });
  }
}

export default Request;

引用

// main.js 

import Request from "./utils/request";

// 初始化实例时,可以传入配置,也可以不传,使用默认配置
// const config = { baseUrl: 'xxx.api.com'}
// const http = new Request(config);
const http = new Request();

// 挂在到uni全局
uni.http = http;

// 挂在到Vue实例
Vue.prototype.http = http;

使用方式

// .vue 文件内调用
this.http.post("table/list", { name: "小明"}, {})
.then((res) => {
    console.log(res);
})
.catch((err) => {
    console.log(err);
});

// 全局调用

uni.http.post(url, data, config)
.then(res=> {
   console.log(res)
})
.catch(err=> {
   console.log(err)
})

使用luch-request插件

对于大多数人来讲,有现成的轮子不用,自己封装那是不可能的。虽然以上对于uni.request的封装只有一百多行代码,但也包含了挺多的知识点,以及对于工具库封装的思考,同时功能也不是特别完善。经过一番调研,luch-request算是做的比较好的请求库,有三万多的下载量,使用的总体感觉,文档挺完善的,使用方式跟axios也比较像,最主要的是功能完善,没发现什么bug,兼容性也挺好的,最终也是选择基于它做进一步的封装。

目录结构

image.png

request封装

// src/utils/request.js


// 引入 luch-request 插件
import luchRequest from "./luch-request/index";

// 默认配置,和luch-request基本一致
const defeaultConfig = {
  baseURL: "",
  header: {},
  method: "POST",
  dataType: "json",
  // #ifndef MP-ALIPAY
  responseType: "text",
  // #endif
  // 注:如果局部custom与全局custom有同名属性,则后面的属性会覆盖前面的属性,相当于Object.assign(全局,局部)
  custom: {}, // 全局自定义参数默认值
  // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
  timeout: 60000,
  // #endif
  // #ifdef APP-PLUS
  sslVerify: true,
  // #endif
  // #ifdef H5
  // 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+)
  withCredentials: false,
  // #endif
  // #ifdef APP-PLUS
  firstIpv4: false, // DNS解析时优先使用ipv4 仅 App-Android 支持 (HBuilderX 2.8.0+)
  // #endif
  // 局部优先级高于全局,返回当前请求的task,options。请勿在此处修改options。非必填
  // getTask: (task, options) => {
  // 相当于设置了请求超时时间500ms
  //   setTimeout(() => {
  //     task.abort()
  //   }, 500)
  // },
  // 全局自定义验证器。参数为statusCode 且必存在,不用判断空情况。
  validateStatus: (statusCode) => {
    // statusCode 必存在。此处示例为全局默认配置
    return statusCode >= 200 && statusCode < 300;
  },
};

// 错误提示
const handleError = (title) => {
  uni.showToast({
    title,
    icon: "none",
  });
};

/**
 * 创建http实例
 * @param {Object} config 用户自定义配置
 */
function request(config) {
  // 对luch-request 实例化,并且合并用户配置和默认配置
  const request = new luchRequest({
    ...defeaultConfig,
    ...config,
  });
  // 请求拦截,可以根据需求自定义配置
  request.interceptors.request.use(
    (config) => {
      uni.showLoading();
      return config;
    },
    (err) => {
      handleError(err.message);
      return Promise.reject(err);
    }
  );
  // 响应拦截,可以根据需求自定义配置
  request.interceptors.response.use(
    async (response) => {
      try {
        uni.hideLoading();
        return await intercept(response);
      } catch (err) {
        handleError(err.message);
        return Promise.reject(err);
      }
    },
    (err) => {
      handleError(err.message);
      return Promise.reject(err);
    }
  );
  return request;
}

/**
 * 响应拦截器,抽成一个方法,比较灵活
 * @param {*} promise
 */
async function intercept(response) {
  const { data } = response || {};
  if (data?.code !== 200) throw new Error(data.message);
  return data?.data;
}

export default request;

引用

// main.js

import request from "./utils/request";

// 初始化实例时,可以传入配置,也可以不传,使用默认配置
// config 配置与luch-request配置一致,可以自定义
// const config = { baseURL: 'xxx.api.com'}
const http = request(config);

// 挂在到uni全局
uni.http = http;

// 挂在到Vue实例
Vue.prototype.http = http;

使用方式

// .vue 文件内调用
// this.http.post(url, data, config)
this.http.post("table/list", { name: "小明"}, {})
.then((res) => {
    console.log(res);
})
.catch((err) => {
    console.log(err);
});

// 全局调用

uni.http.post(url, data, config)
.then(res=> {
   console.log(res)
})
.catch(err=> {
   console.log(err)
})

小结

关于uni-app实现http请求的探索到这里就告一段落了,三种方式都是可以,不过直接使用uni.request还是有些繁琐,缺少了Promise封装和请求拦截,所以可以采用第二种方式,对其进行一定的封装,也能保证一定的可配置性和可维护性。当然,自己的封装,也只能根据需求,做相应的封装,没有第三方轮子那么完善,自己封装也需要一定的封装能力,所以有了luch-request的出场,他的封装跟axios还是比较像的,回到了我们熟悉的节奏。当然,使用第三方库有优点,也有相应的去缺点,由于uni-app的跨端性,可能导致luch-request出现不可用或者未知的错误,也是有可能,所以怎样选择,还是需要根据需求而定。

具体的代码,我写好了源码以及示例,并且发布到Github,可以查看,有问题也欢迎指出。

参考