0. 系列文章

1.使用Typescript重构axios(一)——写在最前面

2.使用Typescript重构axios(二)——项目起手,跑通流程

3.使用Typescript重构axios(三)——实现基础功能:处理get请求url参数

4.使用Typescript重构axios(四)——实现基础功能:处理post请求参数

5.使用Typescript重构axios(五)——实现基础功能:处理请求的header

6.使用Typescript重构axios(六)——实现基础功能:获取响应数据

7.使用Typescript重构axios(七)——实现基础功能:处理响应header

8.使用Typescript重构axios(八)——实现基础功能:处理响应data

9.使用Typescript重构axios(九)——异常处理:基础版

10.使用Typescript重构axios(十)——异常处理:增强版

11.使用Typescript重构axios(十一)——接口扩展

12.使用Typescript重构axios(十二)——增加参数

13.使用Typescript重构axios(十三)——让响应数据支持泛型

14.使用Typescript重构axios(十四)——实现拦截器

15.使用Typescript重构axios(十五)——默认配置

16.使用Typescript重构axios(十六)——请求和响应数据配置化

17.使用Typescript重构axios(十七)——增加axios.create

18.使用Typescript重构axios(十八)——请求取消功能:总体思路

19.使用Typescript重构axios(十九)——请求取消功能:实现第二种使用方式

20.使用Typescript重构axios(二十)——请求取消功能:实现第一种使用方式

21.使用Typescript重构axios(二十一)——请求取消功能:添加axios.isCancel接口

22.使用Typescript重构axios(二十二)——请求取消功能:收尾

23.使用Typescript重构axios(二十三)——添加withCredentials属性

24.使用Typescript重构axios(二十四)——防御XSRF攻击

25.使用Typescript重构axios(二十五)——文件上传下载进度监控

26.使用Typescript重构axios(二十六)——添加HTTP授权auth属性

27.使用Typescript重构axios(二十七)——添加请求状态码合法性校验

28.使用Typescript重构axios(二十八)——自定义序列化请求参数

29.使用Typescript重构axios(二十九)——添加baseURL

30.使用Typescript重构axios(三十)——添加axios.getUri方法

31.使用Typescript重构axios(三十一)——添加axios.all和axios.spread方法

32.使用Typescript重构axios(三十二)——写在最后面(总结)

项目源码请猛戳这里!!!

1. 前言

在官方的axios中,有一个非常重要而且非常好用的功能,那就是:请求和响应拦截器。请求拦截器就是可以在每个请求发送之前为请求做一些额外的东西,例如:我们可以在请求拦截器中为所有的请求添加token认证等信息,添加后再将请求发出。而响应拦截器就是当每个请求的响应回来之后我们可以先对其进行一道预处理,处理后再将响应返回给真正的请求。像如下使用方式:

// 添加一个请求拦截器
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);
}); // 删除一个请求拦截器
const myInterceptor = axios.interceptors.request.use(function () {/*...*/})
axios.interceptors.request.eject(myInterceptor)

axios 对象上有一个 interceptors 对象属性,该属性又有 requestresponse 2 个属性,它们都有一个 use 方法,use 方法支持 2 个参数,第一个参数类似 Promise 的 resolve 函数,第二个参数类似 Promise 的 reject 函数。我们可以在 resolve 函数和 reject 函数中执行同步代码或者是异步代码逻辑。

另外,也可以使用eject删除某个拦截器。

接下来,我们就要在我们的axios中实现该功能。

2. 需求分析

通过观察上面官方给出的使用示例,不管是请求拦截器axios.interceptors.request还是响应拦截器axios.interceptors.response,它们都有一个添加拦截器的use方法和删除拦截器的eject方法。那么我们不妨可以这样想:假设有一个拦截器类,该类上有两个实例方法,分别是添加拦截器的use方法和删除拦截器的eject方法,而请求拦截器和响应拦截器都是该类的实例,当我们在实例化axios时,我们给axios的实例上绑定interceptors.requestinterceptors.response属性,同时这两个属性分别实例化了拦截器类,这样我们就可以通过axios.interceptors.request.use来为axios实例添加拦截器,或者通过axios.interceptors.request.eject来删除拦截器,说的再多,不如来看下代码,伪代码如下:

// InterceptorManager为拦截器类
class InterceptorManager {
use(){ },
eject(){ }
} // Axios是之前创建的Axios类 class Axios {
interceptors: Interceptors constructor() {
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}
} axios = new Axios()
axios.nterceptors.request.use()
axios.nterceptors.request.eject()

OK,需求明确了,我们就先来实现一下拦截器类InterceptorManager

3. 实现拦截器类

3.1 接口定义

根据需求,拦截器类需要包含两个方法:useeject。并且use方法接收 2 个参数,第一个必选参数是 resolve 函数,第二个可选参数是 reject 函数,对于 resolve 函数的参数在请求拦截器和响应拦截器中有所不同,请求拦截器中参数是请求的配置对象config,其类型是 AxiosRequestConfig ,而响应拦截器中参数是响应对象response,其类型是 AxiosResponse 。所以我们的拦截器类类型接口定义如下:

// src/types/index.ts

export interface AxiosInterceptorManager<T> {
use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number eject(id: number): void
} export interface ResolvedFn<T=any> {
(val: T): T | Promise<T>
} export interface RejectedFn {
(error: any): any
}
  • 我们定义的拦截器类型接口AxiosInterceptorManager支持传入一个泛型参数T,而这个T就是根据看创建的是请求拦截器还是响应拦截器对应传入的AxiosRequestConfigAxiosResponse
  • use方法除了接收上面说的两个函数作为参数,它还返回一个创建的拦截器的id,这个id用来标识拦截器;
  • eject方法接收拦截器的id作为参数,用来标明删除哪个拦截器;

3.2 实现拦截器类

接口定义好之后,我们就可以来实现拦截器类了。我们在src/core目录下创建interceptorManager.ts文件

  1. 首先我们创建一个类,类名叫InterceptorManager,在该类的构造函数中我们创建一个数组,用来存放创建的所有拦截器Interceptor

    // src/core/interceptorManager.ts
    
    import { ResolvedFn, RejectedFn } from '../types'
    interface Interceptor<T> {
    resolved: ResolvedFn<T>
    rejected?: RejectedFn
    } export default class InterceptorManager<T> {
    private interceptors: Array<Interceptor<T> | null> constructor() {
    this.interceptors = []
    }
    }

    每个拦截器Interceptor都是一个对象,包含两个属性resolvedrejected,对应use方法的两个函数参数。

  2. 接着,我们为InterceptorManager类中添加use方法

     use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number {
    this.interceptors.push({
    resolved,
    rejected
    })
    return this.interceptors.length - 1
    }

    该方法接收上文说的两个函数参数,我们把这两个函数参数组成一个对象存入第一步创建的数组interceptors中,并且返回该对象在数组中的索引作为该拦截器的id

  3. 最后,我们InterceptorManager类中添加eject方法

    eject(id: number): void {
    if (this.interceptors[id]) {
    this.interceptors[id] = null
    }
    }

    在该方法中,我们之所以没有简单粗暴的使用数组的slice方法将需要删除的拦截器直接剔除掉,是因为如果这样做了之后interceptors数组的长度就会发生变化,而我们在第2步中用拦截器在数组中的索引作为了拦截器的唯一标识id,如果数组长度变了,那么数组里的拦截器id也就变了,所以我们在数组中将需要删除的拦截器置为null,在后面的逻辑中,我们只需判断拦截器为不为null,如果为null不做处理就是了。

OK,拦截器类就已经暂时创建好了。接下来我们就来实现拦截器的调用。

4. 拦截器调用顺序

在实现拦截器的调用之前,我们有必要了解一下当有多个拦截器时官方对每个拦截器的调用先后顺序时怎样的。为此,我们用官方axios写了如下试验代码:

// 请求拦截器1
axios.interceptors.request.use(config => {
config.headers.test += 'requestInterceptors1---'
return config
}) // 请求拦截器2
axios.interceptors.request.use(config => {
config.headers.test += 'requestInterceptors2---'
return config
}) // 响应拦截器1
axios.interceptors.response.use(response => {
response.data.test += '响应拦截器1'
return response
}) // 响应拦截器2
axios.interceptors.response.use(response => {
response.data.test += '响应拦截器2'
return response
}) axios.get("/api/getuser", { headers: { test: "NLRX---" } }).then(res => {
console.log(res);
});

我们为axios分别添加了两个请求拦截器和两个响应拦截器,然后发出一个get请求,我们可以通过观察请求headers里面的test字段,以及响应data里面的test字段,我们就可以看出所有拦截器的调用先后顺序时怎样的。结果如下:



从结果中我们可以看到:

  • 请求拦截器:先添加的后执行,后添加的先执行;
  • 响应拦截器:按添加顺序执行

其实,我们可以用一张图来描述拦截器的工作流程:

OK,以上就是拦截器的调用顺序,了解了这个顺序后,我们就可以来着手实现拦截器的调用了。

5. 实现拦截器调用

实现拦截器调用之前我们先需要思考如下三个问题:

  1. 调用的逻辑应该实现在哪?
  2. 请求和响应拦截器调用分别是有顺序的,并且要先调用请求拦截器,然后再调用响应拦截器,该怎么实现?
  3. 每个拦截器调用都应该是链式的,该怎么实现?

OK,基于以上三个问题,我们分别来解答。

5.1 在哪里实现调用逻辑

我们知道,请求拦截器应该在发出请求之前调用,响应拦截器应该是在响应回来之后调用,所以这两个都跟核心请求功能相关。而我们又知道,在之前的实现方案中,所有的请求都是通过Axios类的request方法发出的,所以自然而然,拦截器的调用应该在request里面实现。

OK,这个问题搞明白以后,我们先根据第2章中的伪代码,在Axios类的构造函数中为Axios实例添加interceptor属性,如下:

// src/core/Axios.ts

import { InterceptorManager } from "./InterceptorManager";
export default class Axios {
private interceptors: {
request: InterceptorManager<AxiosRequestConfig>;
response: InterceptorManager<AxiosResponse<any>>;
};
constructor() {
this.interceptors = {
request: new InterceptorManager<AxiosRequestConfig>(),
response: new InterceptorManager<AxiosResponse>()
};
}
}

Axios实例添加了interceptor属性后别忘了给Axios类类型接口里面添加上这个属性接口:

// src/types/index.ts

export interface Axios {
interceptors: {
request: AxiosInterceptorManager<AxiosRequestConfig>;
response: AxiosInterceptorManager<AxiosResponse>;
};
// ...
}

5.2 实现按顺序调用

通过第4章的试验,我们得知:请求拦截器和响应拦截器调用遵循以下顺序:

  • 请求拦截器:先添加的后调用,后添加的先调用。
  • 响应拦截器:先添加的先调用,后添加的后调用。
  • 请求拦截器调用完后,再调用响应拦截器。

想要实现这种顺序其实也不难,我们可以创建一个数组,把所有拦截器都按照执行的顺序放入该数组,然后按照数组顺序去调用即可,数组可以如下:

arr = ['请求拦截器2','请求拦截器1',...,'真实请求','响应拦截器1','响应拦截器2',...]

OK,办法有了,那就来实现一下:

  1. 我们先创建一个数组arr,该数组内默认只存储真实请求;

    interface PromiseArr<T> {
    resolved: ResolvedFn<T> | ((config: AxiosRequestConfig) => AxiosPromise);
    rejected?: RejectedFn;
    }
    let arr: PromiseArr<any>[] = [
    {
    resolved: dispatchRequest,
    rejected: undefined
    }
    ];
  2. 然后遍历所有的请求拦截器,把每个请求拦截器都从arr的头部插入,这样就可以保证后面添加的在数组中前面,并且处于真实请求之前。

    this.interceptors.request.interceptors.forEach(interceptor => {
    if (interceptor !== null) {
    arr.unshift(interceptor);
    }
    });

    this.interceptors.request.interceptors是所有添加的请求拦截器,然后遍历这些请求拦截器,把不为null(是null的表示被删掉的拦截器)的从arr的头部插入。

  3. 然后遍历所有的响应拦截器,把每个响应拦截器都从arr的尾部插入,这样就可以保证后面添加的在数组中后面,并且处于真实请求之后。

    this.interceptors.response.interceptors.forEach(interceptor => {
    if (interceptor !== null) {
    arr.push(interceptor);
    }
    });

OK,这样我们就把这个调用顺序数组构造好了。

5.3 实现链式调用

要实现链式调用,我们首先想到的就是通过Promise进行链式调用,如下:

let promise = Promise.resolve(config)

while (arr.length) {
const { resolved, rejected } = arr.shift()!;
promise = promise.then(resolved, rejected);
} return promise

定义一个已经 resolvepromise,循环 arr,拿到每个拦截器对象,把它们的 resolved 函数和 rejected 函数添加到 promise.then 的参数中,这样就相当于通过 Promise 的链式调用方式,实现了拦截器一层层的链式调用的效果。

5.4 完整代码

OK,三个问题解决完后,我们的拦截器调用也就实现了,request方法完整代码如下:

import { InterceptorManager } from "./InterceptorManager";

interface PromiseArr<T> {
resolved: ResolvedFn<T> | ((config: AxiosRequestConfig) => AxiosPromise);
rejected?: RejectedFn;
} export default class Axios {
private interceptors: {
request: InterceptorManager<AxiosRequestConfig>;
response: InterceptorManager<AxiosResponse<any>>;
};
constructor() {
this.interceptors = {
request: new InterceptorManager<AxiosRequestConfig>(),
response: new InterceptorManager<AxiosResponse>()
};
}
request(url: any, config?: any): AxiosPromise {
if (typeof url === "string") {
config = config ? config : {};
config.url = url;
} else {
config = url;
} let arr: PromiseArr<any>[] = [
{
resolved: dispatchRequest,
rejected: undefined
}
]; this.interceptors.request.interceptors.forEach(interceptor => {
if (interceptor !== null) {
arr.unshift(interceptor);
}
});
this.interceptors.response.interceptors.forEach(interceptor => {
if (interceptor !== null) {
arr.push(interceptor);
}
});
let promise = Promise.resolve(config); while (arr.length) {
const { resolved, rejected } = arr.shift()!;
promise = promise.then(resolved, rejected);
} return promise;
} // ...
}

接下来,我们就可以编写demo来测试下我们实现的拦截器效果如何。

6. demo编写

examples 目录下创建 interceptor目录,在 interceptor目录下创建 index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>interceptor demo</title>
</head>
<body>
<script src="/__build__/interceptor.js"></script>
</body>
</html>

接着再创建 app.ts 作为入口文件:

import axios from "../../src/axios";

// 请求拦截器1
let requestInterceptor1 = axios.interceptors.request.use(config => {
config.headers.test += "requestInterceptors1---";
return config;
}); // 请求拦截器2
axios.interceptors.request.use(config => {
config.headers.test += "requestInterceptors2---";
return config;
}); // 请求拦截器3
axios.interceptors.request.use(config => {
config.headers.test += "requestInterceptors3---";
return config;
}); // 响应拦截器1
axios.interceptors.response.use(response => {
response.data.test += "响应拦截器1";
return response;
}); // 响应拦截器2
let responseInterceptor2 = axios.interceptors.response.use(response => {
response.data.test += "响应拦截器2";
return response;
}); // 响应拦截器3
axios.interceptors.response.use(response => {
response.data.test += "响应拦截器3";
return response;
}); axios.interceptors.request.eject(requestInterceptor1);
axios.interceptors.response.eject(responseInterceptor2); axios.get("/api/getuser", { headers: { test: "NLRX---" } }).then(res => {
console.log(res);
});

demo 里面我们添加了 3 个请求拦截器,和 3 个响应拦截器,并且删除了第1个请求拦截器和第2个响应拦截器。

路由接口沿用上篇文章的接口,故不需要添加新的路由接口。

最后在根目录下的index.html中加上启动该demo的入口:

<li><a href="examples/interceptor">interceptor</a></li>

OK,我们在命令行中执行:

# 同时开启客户端和服务端
npm run server | npm start

接着我们打开 chrome 浏览器,访问 http://localhost:8000/ 即可访问我们的 demo 了,我们点击 interceptor,通过F12network 部分我们可以看到请求已正常发出:



从结果中,我们可以看到:请求拦截器1被删除了,请求拦截器2和3正常工作,并且顺序也是先3后2;响应拦截器2被删除了,1和3正常工作,并且顺序是先1后3。

OK,拦截器到此就实现完毕了。

(完)

使用Typescript重构axios(十四)——实现拦截器的更多相关文章

  1. 使用Typescript重构axios(十)——异常处理:增强版

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  2. 使用Typescript重构axios(十二)——增加参数

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  3. 使用Typescript重构axios(十五)——默认配置

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  4. 使用Typescript重构axios(十六)——请求和响应数据配置化

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  5. 使用Typescript重构axios(十八)——请求取消功能:总体思路

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  6. 使用Typescript重构axios(十九)——请求取消功能:实现第二种使用方式

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  7. 使用Typescript重构axios(二十四)——防御XSRF攻击

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  8. 使用Typescript重构axios(四)——实现基础功能:处理post请求参数

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  9. 使用Typescript重构axios(二十)——请求取消功能:实现第一种使用方式

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

随机推荐

  1. 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?

    线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间. 一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现.同上一个问题,线程调度并不受到 Java 虚拟 ...

  2. ES6——新增数据结构Set与Map的用法

    ES6 提供了新的数据结构 Set以及Map,下面我们来一一讲解. 一.Set 特性 似于数组,但它的一大特性就是所有元素都是唯一的,没有重复. 我们可以利用这一唯一特性进行数组的去重工作. 1.单一 ...

  3. 运维自动化神器ansible之user模块

    运维自动化神器ansible之user模块 一.概述   user模块 可管理远程主机上的 用户,比如创建用户.修改用户.删除用户.为用户创建密钥对等操作. 二.参数介绍   name: 用于指定操作 ...

  4. Python爬虫工程师必学——App数据抓取实战 ✌✌

    Python爬虫工程师必学——App数据抓取实战 (一个人学习或许会很枯燥,但是寻找更多志同道合的朋友一起,学习将会变得更加有意义✌✌) 爬虫分为几大方向,WEB网页数据抓取.APP数据抓取.软件系统 ...

  5. CentOS 7 环境下修改主机名

    本篇文章简单介绍在CentOS 7的环境下更改主机名的方法步骤. 首先我们开启虚拟机,用root账户进行登陆,并且打开终端.我们看到默认的主机名是我们新建虚拟机时自定义的名称. 接下来我们用命令更改主 ...

  6. Kubernetes网络插件Flannel的三种工作模式

    跨主机通信的一个解决方案是Flannel,由CoreOS推出,支持3种实现:UDP.VXLAN.host-gw 一.UDP模式(性能差) 核心就是通过TUN设备flannel0实现(TUN设备是工作在 ...

  7. web安全之XSS基础-常见编码科普

    0x01常用编码 html实体编码(10进制与16进制): 如把尖括号编码[ < ]  -----> html十进制: <  html十六进制:< javascript的八进制 ...

  8. Ubuntu php + apache

    Ubuntu 环境: 问题1: apache 不能解析 *.php 文件 安装apache的扩展模块 :  apt-get install libapache2-mod-php 问题2 : 客户端访问 ...

  9. Linux本地内核提权漏洞复现(CVE-2019-13272)

    Linux本地内核提权漏洞复现(CVE-2019-13272) 一.漏洞描述 当调用PTRACE_TRACEME时,ptrace_link函数将获得对父进程凭据的RCU引用,然后将该指针指向get_c ...

  10. 百万年薪python之路 -- 小数据池和代码块

    1.小数据池和代码块 # 小数据池 -- 缓存机制(驻留机制) # == 判断两边内容是否相等 # a = 10 # b = 10 # print(a == b) # is 是 # a = 10 # ...