六、实现拦截器功能
本篇我们来实现ts-axios的拦截功能,拦截功能就是可以在请求发送之前和受到服务端接口返回的数据之后做一些工作。
一、一组示例代码
首先来看一段代码来明确一下,这个拦截(interceptors)功能是怎么使用的。
import axios from '../../src/index'
axios.interceptors.request.use(config => {
config.headers.test += '1'
return config
})
axios.interceptors.request.use(config => {
config.headers.test += '2'
return config
})
axios.interceptors.request.use(config => {
config.headers.test += '3'
return config
})
axios.interceptors.response.use(res => {
res.data += '1'
return res
})
let interceptor = axios.interceptors.response.use(res => {
res.data += '2'
return res
})
axios.interceptors.response.use(res => {
res.data += '3'
return res
})
axios.interceptors.response.eject(interceptor)
axios({
url: '/interceptor/get',
method: 'get',
headers: {
test: ''
}
}).then(res => {
console.log(res.data)
})
通过这段代码,我们可以知道,用户通过调用axios.interceptors.request.use()来收集发送之前的请求,调用axios.interceptors.response.use()来收集服务器响应之后的请求。最后我们发送了请求,返回的结果如图所示:
image.png
。
二、具体实现
明确了axios.interceptor的使用,我们来看看其具体的实现过程。
1、定义相关的类型借口
/src/types/index.ts
export interface AxiosInterceptorManager<T>{
use(resolved: ResolvedFn<T>, rejected?: RejectedFn) :number
eject(id: number): void
}
上面的代码中,我们定义了拦截器管理类, 我们使用了泛型的方式,因为拦截器分为请求阶段的拦截和返回阶段的拦截。
类型中,我们定义了两个方法,use()方法负责收集拦截信息,需要传递两个参数,分别是成功和请失败后的回调函数。这两个回调函数的参数分别如下:
export interface ResolvedFn<T>{
(val: T): T | Promise<T>
}
export interface RejectedFn {
(error: any): any
}
2、定义InterceptorManager类
在上面定义了类接口的基础上,我们来实现一下InterceptorManager类,这个类包括了收集拦截功能的use()方法和移除拦截的eject()方法,以及内部使用的forEach()方法,和interceptors私有数组变量。下面分段说明这个类的具体实现
a、定义InterceptorManager类并添加私有变量Interceptor,同样的原因,这个拦截操作分为请求拦截和相应拦截,所以我们同样适用了泛型的结构。
export default class InterceptorManager<T> {
private interceptors: Array<Interceptor<T> | null>
constructor() {
this.interceptors = []
}
}
b、私有变量Interceptor是一个数组,用来存储我们收集到的拦截。我们额外地定义了Interceptor这个接口类。
import { ResolvedFn, RejectedFn } from '../types'
interface Interceptor<T> {
resolved: ResolvedFn<T>
rejected?: RejectedFn
}
c、我们实现用于手机拦截的use方法。该方法我们返回当前interceptors私有数组变量的长度-1,作为该次拦截请求的id,此id可以用于后续我们使用eject()方法来移除拦截器操作。
use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number{
this.interceptors.push({
resolved,
rejected
})
return this.interceptors.length - 1
}
d、实现eject()方法,用于移除指定id的拦截器。此处我们将要移除的拦截器的引用设置为null,并没有使用数组的splice方法直接进行删除,是为了防止我们拦截器存储数组interceptors中,拦截器id的错乱。
eject(id: number): void{
if(this.interceptors[id]){
this.interceptors[id] = null
}
}
e、实现forEach遍历调用方法。这个方法仅仅提供给我们内部使用,它接受一个函数作为参数,返回值为空。
forEach(fn:(Interceptor: Interceptor<T>) => void): void{
this.interceptors.forEach(interceptor => {
if(interceptor !== null){
fn(interceptor)
}
})
}
3、实例化InterceptorManager,正式使用InterceptorManager。
/src/core/Axios.ts
a、为Axios类添加新属性interceptors,并且定义interceptors的类型借口,在Axios构造函数中为其赋值。
interface Interceptors {
request: InterceptorManager<AxiosRequestConfig>
response: InterceptorManager<AxiosResponse>
}
export default class Axios {
interceptors: Interceptors
constructor() {
this.interceptors = {
request: new InterceptorManager<AxiosRequestConfig>(),
response: new InterceptorManager<AxiosResponse>()
}
}
}
interceptors对象的request属性和response属性均是InterceptorManager的实例,因此我们可以调用axios.interceptors.request.use()来收集请求阶段的拦截,调用axios.interceptors.request.eject()来移除请求阶段指定id的拦截,使用axios.interceptors.request.forEach()来一次执行请求阶段的所有拦截操作。同理,调用response的相关方法我们也能对响应阶段的拦截做同样的处理。
b、定义调用promise调用链。这条promise调用连的主要作用就是将、、的所有过程全部实现promise化,可以实现连续的promise链式调用。值得注意的是这条promise链的初始值,我们给resolved赋值dispatchRequest,来开启首次调用。
const chain: PromiseChain<any>[] = [
{
resolved: dispatchRequest,
rejected: undefined
}
]
c、调用axios.interceptors.request.forEach()和axios.interceptors.response.forEach()丰富此链条,并循环执行该所有的拦截。
this.interceptors.request.forEach(interceptor => {
chain.unshift(interceptor)
})
this.interceptors.response.forEach(interceptor => {
chain.push(interceptor)
})
d、以promise方式调用所有的promise链。
let promise = Promise.resolve(config)
while (chain.length) {
const { resolved, rejected } = chain.shift()!
promise = promise.then(resolved, rejected)
}
return promise