VDone Demo VDone Demo
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)

Nikolay Tuzov

Backend Developer
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)
  • 初识 TypeScript

  • TypeScript 常用语法

  • ts-axios 项目初始化

  • ts-axios 基础功能实现

  • ts-axios 异常情况处理

  • ts-axios 接口扩展

  • ts-axios 拦截器实现

    • Interceptor Design and Implementation
      • Requirements Analysis
      • Overall Design
      • Interceptor Manager Class Implementation
        • Interface Definition
        • Code Implementation
      • Chain Call Implementation
      • Writing the Demo
  • ts-axios 配置化实现

  • ts-axios 取消功能实现

  • ts-axios 更多功能实现

  • ts-axios 单元测试

  • ts-axios 部署与发布

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

Interceptor Design and Implementation

Interceptor Design and Implementation

# Requirements Analysis

We want to be able to intercept request sending and responses -- that is, to perform some additional logic before sending requests and after receiving responses.

The desired usage of the interceptors is as follows:

// Add a request interceptor
axios.interceptors.request.use(function (config) {
  // Do something before sending the request
  return config;
}, function (error) {
  // Handle request error
  return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
  // Process response data
  return response;
}, function (error) {
  // Handle response error
  return Promise.reject(error);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

On the axios object there is an interceptors property, which in turn has request and response properties. Both have a use method that accepts 2 parameters: the first is similar to a Promise's resolve function, and the second is similar to a Promise's reject function. We can execute synchronous or asynchronous code logic in the resolve and reject functions.

We can add multiple interceptors, and they are executed in a chained sequential manner. For request interceptors, later-added interceptors execute first in the pre-request process; for response interceptors, earlier-added interceptors execute first after the 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

Additionally, we can support removing a specific interceptor, as follows:

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

# Overall Design

Let's first use a diagram to illustrate the interceptor workflow:

interceptor

The entire process follows a chained call pattern, and each interceptor can support both synchronous and asynchronous processing. This naturally leads us to use Promise chaining to implement the entire call process.

During the execution of this Promise chain, request interceptor resolve functions process the config object, while response interceptor resolve functions process the response object.

After understanding the interceptor workflow, we first need to create an interceptor manager class that allows us to add, remove, and traverse interceptors.

# Interceptor Manager Class Implementation

Based on the requirements, axios has an interceptors property, which in turn has request and response properties that expose a use method to add interceptors. We can think of these two properties as interceptor manager objects. The use method accepts 2 parameters: the first is a resolve function and the second is a reject function. For the resolve function's parameter, request interceptors use the AxiosRequestConfig type while response interceptors use the AxiosResponse type; for the reject function's parameter, the type is any.

Based on the above analysis, let's first define the external interface for the interceptor manager object.

# Interface Definition

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

Here we define the AxiosInterceptorManager generic interface, because the resolve function parameters differ between request interceptors and response interceptors.

# Code Implementation

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

We defined a generic class InterceptorManager that internally maintains a private property interceptors, an array used to store interceptors. The class exposes 3 methods: the use method adds an interceptor to interceptors and returns an id for deletion; the forEach method is for traversing interceptors -- it accepts a function and calls that function during traversal, passing each interceptor as an argument; eject removes an interceptor by its id.

# Chain Call Implementation

This section requires understanding of Promises. You can learn more at mdn (opens new window).

After implementing the interceptor manager class, the next step is to define an interceptors property in Axios with the following type:

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

The Interceptors type has 2 properties: a request interceptor manager class instance and a response interceptor manager class instance. When instantiating the Axios class, we initialize this interceptors instance property in the constructor.

Next, we modify the request method logic to add the interceptor chain call logic:

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

First, we construct an array chain of type PromiseChain and assign the dispatchRequest function to the resolved property. Then we traverse the request interceptors and insert them at the front of chain; then we traverse the response interceptors and append them to the end of chain.

Next, we define an already-resolved promise, loop through chain, get each interceptor object, and add their resolved and rejected functions as arguments to promise.then. This effectively implements the layered chain call effect of interceptors through Promise chaining.

Note the execution order of interceptors: for request interceptors, later-added ones execute first, then earlier-added ones; for response interceptors, earlier-added ones execute first, then later-added ones.

# Writing the Demo

Create an interceptor directory under the examples directory, and create index.html inside the interceptor directory:

<!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

Then create app.ts as the entry file:

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

In this demo, we added 3 request interceptors and 3 response interceptors, then removed the second response interceptor. When running this demo in the browser, the request we send has a test request header with the value 321; the response data returned is hello, and after processing by the response interceptors, the final output is hello13.

At this point, we have implemented the interceptor feature for ts-axios. It is a very practical feature that can be used in real-world scenarios such as login authentication.

Currently, when sending requests through axios, we often pass in a bunch of configuration. However, we also want ts-axios itself to have some default configuration, and we merge the user's custom configuration with the defaults. In fact, most JS libraries work in a similar way. In the next chapter, we will implement this feature.

Edit (opens new window)
#TypeScript
Last Updated: 2026/03/21, 12:14:36
Generic Support for Response Data
Config Merging Design and Implementation

← Generic Support for Response Data Config Merging Design and Implementation→

Recent Updates
01
How I Discovered Disposable Email — A True Story
06-12
02
Animations in Grid Layout
09-15
03
Renaming a Git Branch
08-11
More Articles >
Theme by VDone | Copyright © 2026-2026 Nikolay Tuzov | MIT License | Telegram
  • Auto
  • Light Mode
  • Dark Mode
  • Reading Mode