以配置的方式开关axios拦截器功能

前景提要:
ts 简易封装 axios,统一 API

背景

axios 很多额外功能都是基于拦截器实现。有些功能想要全局使用,因此将拦截器注册在全局。比如重复请求过滤。但也有一小部分请求不希望进行过滤,比如并发上传文件。

因此希望可以在具体的请求方法上,通过配置 config 从而决定针对当前请求,某个拦截器是否启用。

  • 默认不写配置项或者标为 true,都是开启。
  • 明确指定配置项为 false,才是关闭。

注意:在具体方法上配置时,没有明确配置为 true 时,可能没有开启功能。因为在实例化时的 config 对象上,可能将该功能设为 false 了。整个 axios 实例上关闭了该功能,则此时具体方法上必须明确设为 true 覆盖掉实例化的配置才能启用。

以当前请求是否开启响应反馈为例:

typescript 复制代码
// index.ts

import HttpRequest from "./http/request";

// 实例化 axios
const httpRequest = new HttpRequest({
    baseURL: import.meta.env.VITE_APP_API_BASE_URL,
    timeout: import.meta.env.VITE_APP_API_TIMEOUT,
    // 开启响应反馈拦截器功能(注册拦截器后,默认就是开启)
    showMessage: true 
});

// 在该 axios 实例上注册请求响应反馈拦截器
httpRequest.getInstance().interceptors.response.use(responseMessageOnFulfilled, null);
typescript 复制代码
// 默认开启当前请求的请求响应弹窗反馈
async function handleMessageOpen() {
    const res = await httpRequest.get({
        url: "/mock/test"
    });
    console.log("res", res);
}

// 手动关闭当前请求的响应反馈
async function handleMessageClose() {
    const res = await httpRequest.get({
        url: "/mock/test",
        showMessage: false
    });
    console.log("res", res);
}

扩展 config 类型

给 axios config 对象添加额外配置项,首先得扩充它的类型AxiosRequestConfig

typescript 复制代码
// 开关的默认值(默认都是开启)
const DEFAULT_EXTRA_FEATURE_CONFIG = { showMessage: true };

/** 扩展 axios 的请求配置类型 */
export interface MyAxiosRequestConfig<TReqBodyData = any> extends AxiosRequestConfig<TReqBodyData> {
    showMessage?: boolean; // 是否开启请求反馈提示框
}

拦截器中也需要用到 config 对象。但是在最新的 axios 中,请求拦截器中 config 的类型不再是 AxiosRequestConfig,而是InternalAxiosRequestConfig。因此还需要扩展一下请求拦截器 configInternalAxiosRequestConfig类型。

typescript 复制代码
/** 给拦截器使用 */
export interface MyInternalAxiosRequestConfig extends InternalAxiosRequestConfig {
    showMessage?: boolean;
}

有些功能在响应拦截器 onFulfilled 回调中完成,如响应反馈,也有些在 onRejected 回调中完成,如请求重试。因此也需要扩展响应拦截器中的回调,AxiosResponseAxiosError里的 config 对象类型。

typescript 复制代码
export interface MyAxiosResponse extends AxiosResponse {
    config: MyInternalAxiosRequestConfig;
}

export interface MyAxiosError extends AxiosError {
    config: MyInternalAxiosRequestConfig;
}

将额外配置项注入响应拦截器

实现各种功能的拦截器,可能是请求拦截器,也可能是响应拦截器。请求拦截器中可以很轻易拿到具体方法 config 上填写的配置项。但是响应拦截器却不好拿,因为 AxiosResponse 实例上的 config 对象是新生成的,它没有我们额外补充的配置项。

因此,我们需要在请求拦截器中先手动保存用户填写的配置项的值,然后在响应拦截器中手动添加到 config 对象上。并且该响应拦截器必须是最先执行的响应拦截器。

typescript 复制代码
class HttpRequest {
    private readonly instance: AxiosInstance;
		private readonly extraConfig: Record<string, boolean> = DEFAULT_EXTRA_FEATURE_CONFIG;

    constructor(config: MyAxiosRequestConfig) {
      	this.instance = axios.create(config);
    
        // 记录 config 额外配置项
        this.instance.interceptors.request.use((config: MyInternalAxiosRequestConfig) => {
            Object.keys(this.extraConfig).forEach(item => {
                if (config[item as keyof MyInternalAxiosRequestConfig] === false)
                    this.extraConfig[item] = !!config[item as keyof MyInternalAxiosRequestConfig];
            });
            return config;
        });
        // 将配置项补充到响应的 config 对象上
        this.instance.interceptors.response.use(
            (res: any) => {
                Object.keys(this.extraConfig).forEach(item => {
                    res.config[item] = this.extraConfig[item];
                    this.extraConfig[item] = true; // 配置复原成默认开启
                });
                return res;
            },
            (err: any) => {
                Object.keys(this.extraConfig).forEach(item => {
                    err.config[item] = this.extraConfig[item];
                    this.extraConfig[item] = true; // 配置复原成默认开启
                });
                throw err;
            }
        );
    }

    ...
}

请求响应反馈拦截器

typescript 复制代码
// src\api\http\message.ts

import { MyAxiosResponse } from "./request";

// 假设接口:{code: number, data: any, msg: string}
function showMessage(res: MyAxiosResponse) {
    const { data } = res;
    // alert 消息提示
    if (data.code >= 200 && data.code < 300) alert(data.msg);
    return res;
}

/**
 * 响应业务消息提示
 */
export function responseMessageOnFulfilled(res: MyAxiosResponse) {
    if (res.config.showMessage) showMessage(res); // showMessage 为 true,才给出反馈
    return res;
}

使用示例

vue 复制代码
<template>
  <div>
    <button @click="handleMessageOpen">message消息提示开</button>
    <button @click="handleMessageClose">message消息提示关</button>
  </div>
</template>

<script setup lang="ts">
  import HttpRequest from "./http/request";
  import { responseMessageOnFulfilled } from "./http/message";

	// 实例化
  const httpRequest = new HttpRequest({
    baseURL: "/",
    timeout: 10000
  });

  // 注册拦截器
  httpRequest.getInstance().interceptors.response.use(responseMessageOnFulfilled);

  // 反馈默认开
  async function handleMessageOpen() {
    const res = await httpRequest.get({
      url: "/__api/mock/get_test"
      // showMessage: true
    });
    console.log("res", res);
  }

  // 手动关闭反馈
  async function handleMessageClose() {
    const res = await httpRequest.get({
      url: "/__api/mock/get_test",
      showMessage: false
    });
    console.log("res", res);
  }
</script>

结果:

总结

通过在 config 对象上添加额外配置的方式来实现针对某个请求开关某个拦截器的目标。

  1. 扩展 config 类型
  2. 将额外配置项从请求 config 保存到 响应 config 对象上
  3. 编写具体功能的拦截器
相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95271 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大1 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师1 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学1 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端