目录

🌘 实现拦截器

# 需求分析

要对请求和响应进行拦截。

实现如下的功能:

// 添加一个请求拦截器
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);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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

并且可以添加多个拦截器,拦截器的执行顺序是链式依次执行的方式。对于 request 拦截器,后添加的拦截器会在请求前的过程先执行;对于 response 拦截器,先添加的拦截器会在响应后先执行;

axios.interceptors.request.use(config => {
  config.headers.test += '1'
  return config
})
axios.interceptors.request.use(config => {
  config.headers.test += '2'
  return config
})
1
2
3
4
5
6
7
8

也可以支持删除某个拦截器,如下:

const myInterceptor = axios.interceptors.request.use(function () {/*...*/})
axios.interceptors.request.eject(myInterceptor)
1
2

# 整体设计

20200105110744

整个过程是一个链式调用的过程,而且每一个拦截器都可以支持同步和异步处理,自然想到使用 Promise 链的方式实现整个调用过程。

这个 Promise 链的执行过程中,请求拦截器 resolve 函数处理的是 config 对象,响应拦截器 resolve 函数处理的是 response 对象;

了解这个过程后,先要创建一个 拦截器管理类,允许添加、删除和遍历拦截器。

# 拦截器管理类实现

根据需求, axios 拥有一个 interceptors 对象属性,该属性又有 requestresponse 2 个属性,它们对外提供一个 use 方法来添加拦截器,可以把这两个属性看做是一个拦截器管理对象。

user 方法支持两个参数,对于 resolve 函数的参数,请求拦截器是 AxiosRequestConfig 类型的;对于 reject 函数的参数类型则是 any 类型的。

# 定义接口

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
}
1
2
3
4
5
6
7
8
9
10
11
12
13

定义 AxiosInterceptorManager 泛型接口,对于 resolve 函数的参数,请求拦截器和响应拦截器是不同的。

修改 Aixos 的类型:

export interface Axios {
  interceptors: {
    request: AxiosInterceptorManager<AxiosRequestConfig>,
    response: AxiosInterceptorManager<AxiosResponse>
  }

  // ... 
}
1
2
3
4
5
6
7
8

# 代码实现

定义新的类 src/core/InterceptorManager

import { RejectedFn, ResolvedFn } from '../types'

interface Interceptor<T> {
  resolved: ResolvedFn<T>
  rejected: RejectedFn
}

export default class InterceptorManager<T> {
  private interceptors: Array<Interceptor<T> | null>

  constructor() {
    this.interceptors = []
  }

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

    return this.interceptors.length - 1
  }

  forEach(fn: (interceptor: Interceptor<T>) => void): void {
    this.interceptors.forEach(interceptor => {
      if (interceptor != null) fn(interceptor)
    })
  }

  eject(id: number): void {
    if (this.interceptors[id]) {
      this.interceptors[id] = null
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

定义 InterceptorManager 泛型类,内部维护了一个私有属性 interceptors 数组,存储拦截器。该类对外提供三个方法,其中 use 接口添加拦截器到 interceptors ,返回一个 id 用于删除; forEach 接口遍历 interceptors ,支持传入一个函数,遍历的过程会调用该函数,并把每一个 interceptor 作为函数的参数传入; eject 接口删除拦截器,通过传入的拦截器 id 删除。

# 链式调用实现

首先在 src/core/Axios 定义一个 interceptors 属性,类型如下接口:

interface Interceptors {
  request: InterceptorManager<AxiosRequestConfig>
  response: InterceptorManager<AxiosResponse>
}
1
2
3
4

Interceptors 类型有两个属性,一个请求拦截器管理类实例,另一个是响应拦截器管理类实例。在实例化 Aixos 时,在它的构造器去初始化这个 interceptors 实例属性。

然后修改 src/core/AxiosAxios 类中的 request 方法逻辑,添加拦截器链式调用的逻辑:

interface PromiseChain<T> {
  resolved: ResolvedFn<T> | ((config: AxiosRequestConfig) => AxiosPromise)
  rejected?: RejectedFn
}
1
2
3
4

定义 PromiseChain 接口。

request(url: any, config?: any): AxiosPromise {
  if (typeof url === 'string') {
    if (!config) {
      config = {}
    }
    config.url = url
  } else {
    config = url
  }

  const chain: PromiseChain<any>[] = [
    {
      resolved: dispatchRequest,
      rejected: undefined
    }
  ]

  this.interceptors.request.forEach(interceptor => {
    chain.unshift(interceptor)
  })

  this.interceptors.response.forEach(interceptor => {
    chain.push(interceptor)
  })

  let promise = Promise.resolve(config)
  while (chain.length) {
    const { resolved, rejected } = chain.shift()!
    promise = promise.then(resolved, rejected)
  }

  return promise
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

首先构造一个 PromiseChain 类型的数组 chain ,并且把 dispatchRequest 函数赋值给 resolved ;接着先遍历请求拦截器插入到 chain 的前面;然后再遍历响应拦截器插入到 chain 的后面。

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

要注意 拦截器的执行顺序。请求拦截器先执行后添加的,再执行先添加的;对于响应拦截器,先执行先添加的,后执行后添加的。

# 编写 DEMO 代码

examples/interceptor/app.ts

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)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

运行后,可以查看到 请求头中添加了字段 test ,以及内容为 321 ,说明请求拦截器的执行顺序正确;最后控制台输出的内容为 13 ,说明 响应拦截器根据 id 删除成功,并且执行顺序正确。

拦截器是一个非常实用的功能,在实际的工作中可以利用它添加登录权限认证的功能等。

📢 上次更新: 2022/09/02, 10:18:16