使用JS二次封装Axios

一、前言

        正在用vue写一个前端项目,想使用axios库来从后端获取数据。每一次都使用类似“const response = await axios.get(...);”、“const result = response.data;”的方式来请求虽然问题不大,但当接口多起来时,代码的结构耦合度会很高,并且万一不同版本库的调用语法不同,项目后续维护起来也很麻烦。所以参考了这篇博文 (https://juejin.cn/post/7042341684012646407/) 的做法,对axios库进行了二次封装,方便开发过程中api的调用以及后续代码维护。

        虽然不太想做前端/(ㄒoㄒ)/~~,但对我来说也是一次学习(自我安慰),所以还是把过程记录下来,万一日后又用到了,也方便把知识重新拾起。

二、文件结构

        先在src文件夹下建立一个service文件夹,在service文件夹下再分别新建request.js和index.js文件。主要封装过程在request.js下进行。

三、request.js

        request.js的代码如下。对我自己而言,js的语法实在是有点晦涩难懂。一个也许不太准确的理解:

        把attachAPI看作是一个class,class的一个重要成员变量是请求客户端对象client,它通过外部传参来进行初始化。hostApi是一个函数数组,记录attachAPI这个class的所有api成员函数。

        createRequestClient是唯一暴露给外部的函数,createRequestClient函数会先创建一个请求客户端对象client,这个client会被用来作为attachAPI对象的初始化参数,最后把这个attachAPI对象返回给我们使用。

// request.js

import axios from 'axios';

const MATCH_METHOD = /^(GET|POST|PUT|DELETE|HEAD|OPTIONS|CONNECT|TRACE|PATCH)\s+/;
const MATCH_PATH_PARAMS = /:(\w+)/g;
const USE_DATA_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE'];


/**
 * 注册API到Axios实例
 * @param client 客户端对象,类型为AxiosInstance
 * @param apis 该客户端所有待注册的api
 */
function attachAPI(client, apis) {
    const hostApi = Object.create(null);

    for (const apiName in apis) {
        const apiConfig = apis[apiName];
        let apiPath = apiConfig;
        hostApi[apiName] = (params, options) => {
            // e.g. api.createUser({ name: 'xx', avatar: "1" }) => _params={ name: 'xx', avatar: "1" }
            const _params = { ...(params || {}) };

            // e.g. 'POST /api/test' => prefix='POST ', method='POST'
            const [prefix, method] = apiPath.match(MATCH_METHOD) || ['GET ', 'GET'];

            // e.g. 'POST /api/test' => '/api/test'
            let url = apiPath.replace(prefix, '');

            // 匹配以冒号 : 开头、冒号后面跟着一个或多个单词字符(字母、数字或下划线)的字符串
            // 使用全局标志 g,确保在整个字符串中查找所有匹配项,而不仅仅是第一个
            const matchParams = apiPath.match(MATCH_PATH_PARAMS);

            // 匹配请求链接中的动态参数
            if (matchParams) {
                matchParams.forEach((match) => {
                    const key = match.replace(':', '');
                    if (Reflect.has(_params, key)) {
                        url = url.replace(match, Reflect.get(_params, key));
                        Reflect.deleteProperty(_params, key);
                    }
                });
            }

            // 对于某些methods,请求体需要携带数据
            const requestParams = USE_DATA_METHODS.includes(method) 
                ? { data: _params } 
                : { params: _params };

            return client.request({
                url,
                method: method.toLowerCase(),
                ...requestParams,
                ...options,
            });
        };
    }

    return hostApi;
}


/**
 * 创建请求客户端
 * @param requestConfig 客户端初始化配置,包括baseURL、headers、headerHandlers、errorHandler、timeout、apis属性
 */
export function createRequestClient(requestConfig) {
    const client = axios.create({
        baseURL: requestConfig.baseURL,
        headers: requestConfig.headers,
        timeout: requestConfig.timeout,
    });

    // 设置请求拦截器,config 是拦截器接收到的请求配置对象
    client.interceptors.request.use((config) => {
        // 检查 requestConfig 对象中是否存在 headerHandlers 属性,若不存在则赋值为空数组
        // .map() 方法遍历 headerHandlers 数组中的每一个函数,并对每个函数进行调用
        const headerHandlers = (requestConfig.headerHandlers || []).map((handler) => {
            // handler 是从 config.headerHandlers 数组中取出的每一个元素
            return handler(config)
                .then((mixHeaders) => {
                    Object.assign(config.headers, mixHeaders);
                })
                .catch();
        });

        // 确保所有请求头修改动作完成后,返回修改后的 config 对象
        return Promise.all(headerHandlers).then(() => config);
    });

    // 设置响应拦截器
    client.interceptors.response.use(
        // 成功回调
        (res) => res,
        // 错误回调
        (error) => {
            const requestError = requestConfig.errorHandler 
                ? requestConfig.errorHandler(error) 
                : error;
            // 返回一个被拒绝的 Promise,确保调用链中的 .catch() 方法能够捕获到这个错误
            return Promise.reject(requestError);
        },
    );

    return attachAPI(client, requestConfig.apis);
}

四、index.js

        

// index.js

import { createRequestClient } from './request';

/**
 * 请求客户端的初始化参数
 * @var BASE_URL base链接
 * @var HEADER 静态接口请求头
 * @var HEADER_HANDLERS 动态接口请求头
 * @var ERROR_HANDLERS 错误处理函数
 * @var TIME_OUT 请求超时时间(ms)
 */
const BASE_URL = '/api_test';
const HEADER = {
    'Content-Type': 'application/json',
};
const HEADER_HANDLERS = [
    (config) => {
        const token = localStorage.getItem('token');
        if (token) {
            return Promise.resolve({ 'Authorization': `Bearer ${token}` });
        }
        return Promise.resolve({});
    },
];
const ERROR_HANDLERS = (error) => {
    console.log(error.message);
};
const TIME_OUT = 1500;


export const api = createRequestClient({
    baseURL: BASE_URL,
    header: HEADER,
    headerHandlers: HEADER_HANDLERS,
    errorHandler: ERROR_HANDLERS,
    timeout: TIME_OUT,
    apis: {
        getCameraState: 'GET /camera/state/:id',
        startGrabbing: 'PUT /camera/grab/:id',
    }
});

       

五、调用方法

import { api } from '@/service/index';

api.getCameraState({ id: 1 }).then(res => console.log(res.data));
api.startGrabbing({ id: 1, mode: "rgb8" }).then(res => console.log(res.data));

        其实就是:

api.方法({ 参数 }).then(res => {});

         传递参数时,参数通过键值对来表示。

        当参数代表url中的动态参数时,键值对的键需要和url的动态参数名字相同,例如:api.getCameraState({ id: 1 }) 的键名 id 对应 /camera/state/:id 中的id;

        当请求方法是['POST', 'PUT', 'PATCH', 'DELETE']中的其中一个时,请求体携带的数据就是所有传递参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值