浅谈应用中http请求的封装

1,659 阅读5分钟

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」。


前言

开门见山,本篇文章,将分享一下自己平时在工作中常应用到的http请求的封装思路和方法。由于笔者工作中最常用的框架是Vue,自然而然,本文是基于axios进行展开的

为什么要对axios进行封装

可能有同学会问到:“axios明明已经非常好了,功能都非常齐全了,要啥有啥,为什么还要花时间费劲封装多一次,要什么自行车?”

对于提出这种问题的同学,我只能说:“问得好,看完文章之后,下次不要再问了”

思考以下几个问题:

  1. 如果要统一设置请求头,你会怎么做
  2. 如果要针对特定接口拦截返回值并处理数据,你会怎么做
  3. 如果要统一设置loading动画等,你会怎么做

不难看出,以上的问题有一个共同点,就是 统一处理 ,这也就是为什么需要进行封装的原因了

怎么封装

工程目录结构

image.png

首先我们会在项目工程目录下创建一个 service的文件夹 用来存放请求类,然后又在文件夹内创建一个 http的文件夹 用来存放 构造器、拦截器、请求出口 ,而至于 aip1文件夹 这个便是用于存放具体请求的地方了(可以以接口划分,也可以以页面结构划分,也可以以组件结构划分,具体以实际项目为准)

详细讲解

拦截器

从上面截图可以看到,interceptors.js 文件就是用来存放拦截器的,拦截器的作用是什么呢

在拦截器中,你可以针对请求方法、请求参数、接口回调参数、请求异常、响应异常等做统一处理

回到本文开头的三个问题,问题2 3 即可在拦截器内进行处理

// service/http/interceptors.js
// http请求拦截器
const request = config => {
    // 这里实现http请求全局拦截 config为axios请求配置项
    if (config.method === 'get') {
        // do something
    }
    if (config.method === 'post') {
        // do something
    }
    return config;
};

const requestError = err => {
    const { config, code, message } = err;
    // 在此可以做一些异常监控 譬如错误接口,错误信息等收集 用于线上服务排查
    // do something
	
    // 因为axios是返回promise实体,故在错误拦截中,返回空数据,确保接口响应无异常
    return Promise.reject({ code, data: {} });
};

// http响应拦截器
// 响应拦截器一般配合缓存策略 对接口进行缓存时使用
const response = res => {
    const { config, data } = res;
    // do something
    return res.data;
};

const responseError = err => {
    const { config, code, message } = err;
    // 在此可以做一些异常监控 譬如错误接口,错误信息等收集 用于线上服务排查
    // do something
	
    // 这里也可以配合业务缓存策略 对错误接口调用缓存数据,确保服务正常
    // do something
	
    // 针对不同接口是否需要调用缓存数据 返回不同实体
    // return data (data取自缓存)
    return Promise.reject({ code, data: {} });
};

export default {
    request,
    requestError,
    response,
    responseError
};

拦截器生产中常发挥到的用途,已在代码块及上文提及,更多用法可以拓展一下结合业务(肯定可以玩得很离谱)

构造器

顾名思义,构造器,即是构造请求实例的类啦

在构造器中,我们可以设置业务中所需的接口域名,并将拦截器的实例传入axios实例方法中

值得注意的是,虽然我们也可以在构造器中对请求方法进行业务处理,但是为了保证单一原则,一物只做一事,所以在构造器里,还是让它本本分分构造axios实例就好了

// service/http/fetch.js
import axios from 'axios';
import intercepors from './interceptors';

// 创建不同axios实体
let fetch1 = createFetchByHost('//host1.cn', true);
let fetch2 = createFetchByHost('//host2.cn', false);

// axios实体构造函数 可以根据业务需求自行修改
/**
 * 创建fetch
 * @param {string} url
 */
function createFetchByHost(url, withCredentials = false) {
    let http = axios.create({
        baseURL: process.env.NODE_ENV === 'production' ? url : '',
        timeout: 5000,
        withCredentials: withCredentials,
        headers: { post: { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' } }
    });
    // 配置请求头公共部分
    http.interceptors.request.use(intercepors.request, intercepors.requestError);
    http.interceptors.response.use(intercepors.response, intercepors.responseError);
    return http;
}

export default { fetch1, fetch2 };
export { fetch1, fetch2 };
请求出口

请求出口就没什么好讲的了,它所做的事情仅仅是把构造器构造的axios实例暴露出去,直接贴示例代码

// service/http/index.js
import { fetch1, fetch2 } from './fetch';

class Api {
    constructor() {
        this.fetch1 = fetch1;
        this.fetch2 = fetch2;
    }
}

export default Api;
export { Api };
业务接口封装

我们在上面讲解了拦截器、构造器、请求出口,基本上一个半成品就快出来了,现在我们基于半成品,对特定业务接口再封装一下,便大功告成

我们在构造器中,针对不同域名构造了不同的axios实例,在这里,我们将应用这些实例,再构造出特定的接口类。这样,我们就可以针对特定的接口进行统一处理

文章开头的问题3 即可以在这里进行处理。除此之外,我们还可以结合拦截器,对指定域名做接口缓存等

// service/api1/index.js
import Api from '../http';

class Api1 extends Api {
    async apiA({ params = '' }) {
        return this.fetch1.post('/api/path', { params }).catch(() => ({ result: 'error', data: {} }));
    }
}

export default new Api1();
service类出口

这里也是封装的最后一步,将我们上述的步骤整完之后,将service类暴露出去提供给业务使用

// service/index.js
import { fetch } from './http';
import api1 from './api1';

const install = VUE => {
    VUE.prototype.$http = fetch;
    VUE.prototype.$httpApi1 = api1;
};

export default { api1, install };

export { api1 as api1Service, install };

在暴露service类的时候,我们将axios实例和业务接口实例赋值给了vue原型属性,这个根据业务进行调整,我个人倾向是这样干的,毕竟比较方便...

如何在应用

怎么应用这事,就没必要花过多笔墨了,直接贴个示例吧

/*****调用*****/
// 在引用vue实例的组件内
// this.$httpApi1.apiA()

// 在纯js方法内 
// import { api1Service } from '@/service';
// api1Service.apiA()