Will's blog Will's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • VUE
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

will

前端小学生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • VUE
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 初识 TypeScript

  • TypeScript 常用语法

  • ts-axios 项目初始化

  • ts-axios 基础功能实现

  • ts-axios 异常情况处理

  • ts-axios 接口扩展

  • ts-axios 拦截器实现

    • 拦截器设计与实现
      • 需求分析
      • 整体设计
      • 拦截器管理类实现
        • 接口定义
        • 代码实现
      • 链式调用实现
      • demo 编写
  • ts-axios 配置化实现

  • ts-axios 取消功能实现

  • ts-axios 更多功能实现

  • ts-axios 单元测试

  • ts-axios 部署与发布

  • 《TypeScript 从零实现 axios》
  • ts-axios 拦截器实现
HuangYi
2020-01-05
目录

拦截器设计与实现

拦截器设计与实现

# 需求分析

我们希望能对请求的发送和响应做拦截,也就是在发送请求之前和接收到响应之后做一些额外逻辑。

我们希望设计的拦截器的使用方式如下:

// 添加一个请求拦截器

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 对象属性,该属性又有 request 和 response 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

# 整体设计

我们先用一张图来展示一下拦截器工作流程:

interceptor

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

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

在了解了拦截器工作流程后,我们先要创建一个拦截器管理类,允许我们去添加 删除和遍历拦截器。

# 拦截器管理类实现

根据需求,axios 拥有一个 interceptors 对象属性,该属性又有 request 和 response 2 个属性,它们对外提供一个 use 方法来添加拦截器,我们可以把这俩属性看做是一个拦截器管理对象。use 方法支持 2 个参数,第一个是 resolve 函数,第二个是 reject 函数,对于 resolve 函数的参数,请求拦截器是 AxiosRequestConfig 类型的,而响应拦截器是 AxiosResponse 类型的;而对于 reject 函数的参数类型则是 any 类型的。

根据上述分析,我们先来定义一下拦截器管理对象对外的接口。

# 接口定义

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

}

1
2
3
4
5
6
7
8
9
10
11
12
13

这里我们定义了 AxiosInterceptorManager 泛型接口,因为对于 resolve 函数的参数,请求拦截器和响应拦截器是不同的。

# 代码实现

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 = []

  }



  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
36

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

# 链式调用实现

本小节需要你对 Promise 掌握和理解,可以前往 mdn (opens new window) 学习。

当我们实现好拦截器管理类,接下来就是在 Axios 中定义一个 interceptors 属性,它的类型如下:

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>()

    }

  }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Interceptors 类型拥有 2 个属性,一个请求拦截器管理类实例,一个是响应拦截器管理类实例。我们在实例化 Axios 类的时候,在它的构造器去初始化这个 interceptors 实例属性。

接下来,我们修改 request 方法的逻辑,添加拦截器链式调用的逻辑:

core/Axios.ts:

interface PromiseChain {

  resolved: ResolvedFn | ((config: AxiosRequestConfig) => AxiosPromise)

  rejected?: RejectedFn

}



request(url: any, config?: any): AxiosPromise {

  if (typeof url === 'string') {

    if (!config) {

      config = {}

    }

    config.url = url

  } else {

    config = url

  }



  const chain: PromiseChain[] = [{

    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
34
35
36
37

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

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

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

# demo 编写

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

<!DOCTYPE html>

<html lang="en">

  <head>

    <meta charset="utf-8">

    <title>Interceptor example</title>

  </head>

  <body>

    <script src="/__build__/interceptor.js"></script>

  </body>

</html>

1
2
3
4
5
6
7
8
9
10

接着创建 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

该 demo 我们添加了 3 个请求拦截器,添加了 3 个响应拦截器并删除了第二个。运行该 demo 我们通过浏览器访问,我们发送的请求添加了一个 test 的请求 header,它的值是 321;我们的响应数据返回的是 hello,经过响应拦截器的处理,最终我们输出的数据是 hello13。

至此,我们给 ts-axios 实现了拦截器功能,它是一个非常实用的功能,在实际工作中我们可以利用它做一些需求如登录权限认证。

我们目前通过 axios 发送请求,往往会传入一堆配置,但是我们也希望 ts-axios 本身也会有一些默认配置,我们把用户传入的自定义配置和默认配置做一层合并。其实,大部分的 JS 库都是类似的玩法。下面一章我们就来实现这个 feature。

编辑此页 (opens new window)
#TypeScript
上次更新: 2024/08/22, 14:42:43
响应数据支持泛型
合并配置的设计与实现

← 响应数据支持泛型 合并配置的设计与实现→

最近更新
01
我用AI写前端代码这一年:从怀疑到真香的转变
09-15
02
基于 Next.js 的无人机数据孪生可视化平台实践
07-17
03
vite 包缓存问题 处理
06-04
更多文章>
Theme by Vdoing | Copyright © 2019-2025 Will | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式