乐趣区

关于前端:如何优雅的管理-HTTP-请求和响应拦截器

本文思路来自理论我的项目的重构总结,欢送纠正和交换。如果对你有帮忙,还请点赞👍珍藏反对一下啦。

最近重构一个老我的项目,发现其中解决申请的拦截器写得相当乱,于是我将整个我的项目的申请解决层重构了,目前曾经在我的项目中失常运行。

本文会和大家分享我的重构思路和后续优化的思考,为不便与大家分享,我用 Vue3 实现一个简略 demo,思路是统一的,有趣味的敌人能够在我 Github 查看,本文会以这个 Vue 实现的 demo 为例介绍。

本文我会次要和大家分享以下几点:

  1. 问题剖析和方案设计;
  2. 重构后成果;
  3. 开发过程;
  4. 前期优化点;

如果你还不分明什么是 HTTP 申请和响应拦截器,那么能够先看看《77.9K Star 的 Axios 我的项目有哪些值得借鉴的中央》。

一、需要思考和方案设计

1. 问题剖析

目前旧我的项目通过多位共事参加开发,拦截器存在以下问题:

  • 代码比拟凌乱,可读性差;
  • 每个拦截器职责凌乱,存在相互依赖;
  • 逻辑上存在问题;
  • 团队外部不同我的项目无奈复用;

2. 方案设计

剖析下面问题后,我初步的计划如下:
参考 插件化架构设计 独立每个拦截器 ,将每个拦截器抽离成独自文件维护,做到 职责繁多 ,而后通过 拦截器调度器 进行调度和注册。

其拦截器调度过程如下图:

二、重构后成果

代码其实比较简单,这里先看下最初实现成果:

1. 目录分层更加清晰

重构后申请解决层的目录分层更加清晰,大抵如下:

2. 拦截器开发更加不便

在后续业务拓展新的拦截器,仅需 3 个步骤既能够实现拦截器的开发和应用,拦截器调度器会 主动调用所有拦截器

3. 每个拦截器职责更加繁多,可插拔

将每个拦截器抽成一个文件去实现,让每个拦截器 职责拆散且繁多,当不须要应用某个拦截器时,随时能够替换,灵便插拔。

三、开发过程

这里以我独自抽出来的这个 demo 我的项目为例来介绍。

1. 初始化目录构造

依照后面设计的计划,首先须要在我的项目中创立一下目录构造:

- request
    - index.js      // 拦截器调度器
  - interceptors  
    - request     // 用来寄存每个申请拦截器
        - index.js  // 治理所有申请拦截器,并做排序
    - response    // 用来寄存每个响应拦截器
        - index.js  // 治理所有响应拦截器,并做排序

2. 定义拦截器调度器

因为我的项目采纳 axios 申请库,所以咱们须要先晓得 axios 拦截器的应用办法,这里简略看下 axios 文档上如何应用拦截器的:

// 增加申请拦截器
axios.interceptors.request.use(function (config) {
    // 业务 逻辑
    return config;
  }, function (error) {
    // 业务 逻辑
    return Promise.reject(error);
  });

// 增加响应拦截器
axios.interceptors.response.use(function (response) {
    // 业务 逻辑
    return response;
  }, function (error) {
    // 业务逻辑
    return Promise.reject(error);
  });

从下面代码,咱们能够晓得,应用拦截器的时候,只需调用 axios.interceptors 对象上对应办法即可,因而咱们能够将这块逻辑抽取进去:

// src/request/interceptors/index.js
import {log} from '../log';
import request from './request/index';
import response from './response/index';

export const runInterceptors = instance => {log('[runInterceptors]', instance);
      if(!instance) return;

    // 设置申请拦截器
    for (const key in request) {
        instance.interceptors.request
            .use(config => request[key](config));
    }

    // 设置响应拦截器
    for (const key in response) {
        instance.interceptors.response
            .use(result => response[key](result));
    }

    return instance;
}

这就是咱们的 外围拦截器调度器,目前实现导入所有申请拦截器和响应拦截器后,通过 for 循环,注册所有拦截器,最初将整个 axios 实例返回进来。

3. 定义简略的申请拦截器和响应拦截器

这里咱们做简略演示,创立以下两个拦截器:

  1. 申请拦截器:setLoading,作用是在发动申请前,显示一个全局 Toast 框,提醒“加载中 …”文案。
  2. 响应拦截器:setLoading,作用是在申请响应后,敞开页面中的 Toast 框。

为了对立开发标准,咱们约定插件开发标准如下:

/*
  拦截器名称:xxx
*/
const interceptorName = options => {log("[interceptor.request]interceptorName:", options);
    // 拦截器业务
  return options;
};

export default interceptorName;

首先创立文件 src/request/interceptors/request/ 目录下创立 setLoading.js 文件,依照下面约定的插件开发标准,咱们实现上面插件开发:

// src/request/interceptors/request/setLoading.js

import {Toast} from 'vant';
import {log} from "../../log";

/*
  拦截器名称:全局设置申请的 loading 动画
*/
const setLoading = options => {log("[interceptor.request]setLoading:", options);

  Toast.loading({
    duration: 0,
    message: '加载中...',
    forbidClick: true,
  });
  return options;
};

export default setLoading;

而后在导出该申请拦截器,并且导出的是个 数组,不便拦截器调度器进行对立注册:

// src/request/interceptors/request/index.js

import setLoading from './setLoading';

export default [setLoading];

依照雷同形式,咱们开发响应拦截器:

// src/request/interceptors/response/setLoading.js

import {Toast} from 'vant';
import {log} from "../../log";

/*
  拦截器名称:敞开全局申请的 loading 动画
*/
const setLoading = result => {log("[interceptor.response]setLoading:", result);

  // example: 申请返回胜利时,敞开所有 toast 框
  if(result && result.success){Toast.clear();
  }
  return result;
};

export default setLoading;

导出响应拦截器:

// src/request/interceptors/response/index.js

import setLoading from './setLoading';
export default [setLoading];

4. 全局设置 axios 拦截器

依照后面雷同步骤,我又多写了几个拦截器:
申请拦截器:

  • setSecurityInformation.js:为申请的 url 增加平安参数;
  • setSignature.js:为申请的申请头增加加签信息;
  • setToken.js:为申请的申请头增加 token 信息;

响应拦截器:

  • setError.js:解决响应后果的出错状况,如敞开所有 toast 框;
  • setInvalid.js:解决响应后果的登录生效状况,如跳转到登录页;
  • setResult.js:解决响应后果的数据嵌套太深的问题,将 result.data.data.data 这类返回后果解决成 result.data 格局;

至于是如何实现的,大家有趣味能够在我 Github 查看。

而后咱们能够将 axios 进行二次封装,导出 request 对象供业务应用:

// src/request/index.js

import axios from 'axios';
import {runInterceptors} from './interceptors/index';
export const requestConfig = {timeout: 10000};

let request = axios.create(requestConfig);
request = runInterceptors(request);

export default request;

到这边就实现。

在业务中须要发动申请,能够这么应用:

<template>
  <div><button @click="send"> 发动申请 </button></div>
</template>

<script setup>
import request from './../request/index.js';

const send = async () => {
  const result = await request({
    url: 'https://httpbin.org/headers',
    method: 'get'
  })
}
</script>

5. 测试一下

开发到这边就差不多,咱们发送个申请,能够看到所有拦截器执行过程如下:

看看申请头信息:

能够看到咱们开发的申请拦截器曾经失效。

四、Taro 中应用

因为 Taro 中曾经提供了 Taro.request 办法作为申请办法,咱们能够不须要应用 axios 发申请。

基于下面代码进行革新,也很简略,只须要更改 2 个中央:

1. 批改封装申请的办法

次要是更换 axios 为 Taro.request 办法,并应用 addInterceptor  办法导入拦截器:

// src/request/index.js

import Taro from "@tarojs/taro";
import {runInterceptors} from './interceptors/index';

Taro.addInterceptor(runInterceptors);

export const request = Taro.request;
export const requestTask = Taro.RequestTask; // 看需要,是否须要
export const addInterceptor = Taro.addInterceptor; // 看需要,是否须要

2. 批改拦截器调度器

因为 axios 和 Taro.request 增加拦截器的办法不同,所以也须要进行更换:

import request from './interceptors/request';
import response from './interceptors/response';

export const interceptor = {
    request,
    response
};

export const getInterceptor = (chain = {}) => {
  // 设置申请拦截器
  let requestParams = chain.requestParams;
  for (const key in request) {requestParams = request[key](requestParams);
  }

  // 设置响应拦截器
  let responseObject = chain.proceed(requestParams);
  for (const key in response) {responseObject = responseObject.then(res => response[key](res));
  }
  return responseObject;
};

具体 API 能够看 Taro.request 文档,这里不过多介绍。

五、我的项目总结和思考

这次重构次要是依照已有业务进行重构,因而即便是重构后的申请层,依然还有很多能够优化的点,目前我想到有这些,也算是我的一个 TODO LIST 了:

1. 将申请层独立成库

因为公司当初独立站点的我的项目较多,思考到我的项目的对立开发标准,能够思考将该申请层独立为公有库进行保护。
目前思路:

  • 参考插件化架构设计,通过 lerna 做治理所有拦截器;
  • 降级 TypeScript,方便管理和开发;
  • 进行工程化革新,退出构建工具、单元测试、UMD 等等;
  • 应用文档和开发文档欠缺。

2. 反对可更换申请库

独自抽这一点来讲,是因为目前咱们前端团队应用的申请库较多,比拟扩散,所以思考到通用性,须要减少反对可更换申请库办法。
目前思路:

  • 在已有申请层再形象一层 申请库适配层,定义对立接口;
  • 内置几种常见申请库的适配。

3. 开发拦截器脚手架

这个的目标其实很简略,让团队内其他人间接应用脚手架工具,依照内置脚手架模版,疾速创立一个拦截器,进行后续开发,很大水平对立拦截器的开发标准。
目前思路:

  • 内置两套拦截器模版:申请拦截器和响应拦截器;
  • 脚手架开发比较简单,参数(如语言)依据业务须要再确定。

4. 加强拦截器调度

目前实现的这个性能还比较简单,还是得思考加强拦截器调度。
目前思路:

  • 解决拦截器失败的状况;
  • 解决拦截器调度程序的问题;
  • 拦截器同步执行、异步执行、并发执行、循环执行等等状况;
  • 可插拔的拦截器调度;
  • 思考参考 Tapable 插件机制;

六、本文总结

本文通过一次简略的我的项目重构总结出一个申请层拦截器调度计划,目标是为了实现所有 拦截器职责繁多 、不便保护,并 对立保护 主动调度,大大降低理论业务的拦截器开发上手难度。

后续我仍有很多须要优化的中央,作为本人的一个 TODO LIST,如果是做成齐全通用,则定位可能更偏差于拦截器调度容器,只提供一些通用拦截器,其余还是由开发者定义,库负责调度,但罕用的申请库个别都曾经做好,所以这样做的价值有待衡量。

当然,目前还是优先作为团队外部公有库进行开发和应用,因为基本上团队外部应用的业务都差不多,只是我的项目不同。

退出移动版