Interceptor Design and Implementation
# Requirements Analysis
We hope to make interception of sending and responding to requests, that is, before sending the request and receiving a response after doing some additional logic to.
We wanted to use the interceptors are as follows:
// 添加一个请求拦截器
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); });
In axios
there is a target on interceptors
the object attribute another request
and response
two properties, they have a use
method, use
the method supports two parameters, the first parameter Promise similar resolve
function, the second parameter Promise similar reject
function. We can resolve
function and reject
perform synchronization code or function code logic is asynchronous.
And we can add multiple interceptor, the interceptor's execution order is the way followed by the implementation of the chain. For the request
interceptor, after addition of the interceptor will first perform the process before the request; for response
interceptor, first add the interceptor will first executed 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 })
In addition, we can also support delete a blocker, as follows:
const myInterceptor = axios.interceptors.request.use(function () {/*...*/}) axios.interceptors.request.eject(myInterceptor)
# The overall design
Let's use a diagram to show how the interceptor workflow:
The whole process is a chain called way, and each interceptor can support synchronous and asynchronous processing, we naturally think of ways to use Promise chain to implement the call processing.
In the process performed Promise chain, the request interceptor resolve
function processing is config
the object, and the corresponding interceptor resolve
function processing is response
the object.
In the understanding of the interceptor workflow, we must first create an interceptor management class that allows us to add and delete traverse interceptors.
# Interceptor management class implementation
According to requirements, axios
has a interceptors
target property, which there request
and response
two properties, which provide external a use
way to add interceptors, we can put both attributes seen as an interceptor managed objects. use
The method that supports two arguments, the first is a resolve
function, a second reject
function for the resolve
function parameters, it is intercepting the request AxiosRequestConfig
type, and the response are interceptors AxiosResponse
type; for reject
parameter type is a function of any
the type.
According to the above analysis, we first define what interceptor managed objects external interface.
# Interface definitions
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 }
Here we define the AxiosInterceptorManager
generic interface, as for the resolve
function parameters, and in response to the request interceptor interceptors are different.
# 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 } } }
We define a InterceptorManager
generic class, internal maintains a private property interceptors
, it is an array to store the interceptor. The class also provides three methods of external, in which use
the interface is added to the interceptor interceptors
and returns a id
for deletion; forEach
the interface is to walk interceptors
with, it passed a support function, traversing process will call the function, and to every a interceptor
as a parameter of the function passed; eject
that is, delete an interceptor, the interceptor by passing id
deleted.
# Chained calls to achieve
Promise This section requires you to grasp and understand, can go mdn learning.
When we manage to achieve a good interceptor class, the next step is in Axios
the definition of a interceptors
property, its type as follows:
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
Type 2 has property, a management request interceptor class instance, in response to a management class interceptor instance. We instantiate the Axios
class, in its constructor to initialize this interceptors
instance properties.
Next, we modify request
the logical method to add logic interceptor chain calls:
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 }
First, a configuration of PromiseChain
the type of array chain
and the dispatchRequest
functions assigned to the resolved
attribute; first traversal request interceptor then inserted into the chain
front; then traverse the response blocker is inserted into chain
the back.
Next, the definition of an already resolve promise
, this cycle chain
, each interceptor get objects to their resolved
functions and rejected
add functions to promise.then
the parameter, this is equivalent to the chain through invocation Promise, to achieve a layer interceptors the effect of chained calls.
Note that we interceptor execution order, request interceptors, perform post-added, and then performing first added; the response blocker added to the first implementation, added after execution.
# Write demo
In examples
Creating directory interceptor
directory in the interceptor
created directory 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>
Then create app.ts
a file entry:
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) })
The demo we added three request interceptors, added 3 interceptors response and removes a second. We run the demo accessed through a browser, we sent a request to add a test
request header, its value is 321
; our response data is returned hello
, after the response processing interceptor, our final output data is hello13
.
At this point, we have to ts-axios
realize the interceptor function, it is a very useful feature, in practical work, we can use it to do some of the requirements, such as logging certification authority.
We currently by axios
sending a request, they tend to pass a bunch of configuration, but we also want ts-axios
itself will have some default configuration, we pass the user's custom configuration and the default configuration to do one merger. In fact, most of the JS libraries are similar play. The following chapter we have to implement this feature.