需求
我们希望能对请求的发送和响应做拦截,也就是在发送请求之前和接收到响应之后做一些额外逻辑。我们希望设计的拦截器使用方式如下:
axis.interceptors.request.use(function(config) {
// do something
return config
}, function(error) {
return Promise.reject(error)
})
axis.interceptors.response.use(function(response) {
// do something
return response
}, function(error) {
return Promise.reject(error)
})
在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
})
此外,我们也支持删除某个拦截器,如:
const myInterceptor = axios.interceptors.request.use(function () {/*...*/})
axios.interceptors.request.eject(myInterceptor)
整体设计
以下是拦截器工作流程:
整个过程是一个链式调用的过程,并且每个拦截器都可以支持同步和异步处理,我们会想到使用Promise链的方式来实现整个调用过程。
在这个promise链的执行过程中, 请求拦截器resolve函数处理的是config对象,而响应拦截器resolve函数处理的是response对象。
那么接下来我们需要创建一个拦截器管理器,允许我们去添加、删除和遍历拦截器。
拦截器管理类实现
根据需求,axios拥有一个interceptors属性,该属性又有request和response 2个属性,它们
对外提供一个use方法来添加拦截器,我们可以把该属性看作是一个拦截器管理对象。use方法支持2个参数,第一个resolve函数,第二个是reject函数,对于resolve函数的参数,请求拦截器是AxiosRequestConfig类型的,而响应拦截器是AxiosResponse类型的;而对于reject函数的参数类型则any类型的。
根据上述分析,我们先来定义一下拦截器管理对象对外的接口。
接口定义
interface AxiosInterceptorManager {
// 这里返回一个数字,作为interceptor的ID,后面做删除的时候会用到
use(resolved: ResolveFn, rejected?: RejectedFn): number
eject(id: number): void
}
interface ResolveFn {
(val: T): T | Promise
}
interface RejectedFn {
(error: any): any
}
interface Interceptors {
request: InterceptorManager
response: InterceptorManager
}
interceptorManager实现
src/core/interceptorManager.ts
import { ResolveFn, RejectedFn } from "../types";
interface Interceptor {
resolved: ResolveFn
rejected?: RejectedFn
}
export default class InterceptorManager {
private interceptors: Array | null>
constructor() {
this.interceptors = []
}
use(resolved: ResolveFn, rejected?: RejectedFn): number {
this.interceptors.push({
resolved,
rejected
})
return this.interceptors.length - 1
}
eject(id: number): void {
if (this.interceptors[id]) {
this.interceptors[id] = null
}
}
forEach(fn: (Interceptor: Interceptor) => void): void {
this.interceptors.forEach(interceptor => {
if (interceptor !== null) {
fn(interceptor)
}
})
}
}
interface Axios {
interceptors: Interceptors
request(config: AxiosRequestConfig): AxiosPromise
get(url: string, config?: AxiosRequestConfig): AxiosPromise
head(url: string, config?: AxiosRequestConfig): AxiosPromise
delete(url: string, config?: AxiosRequestConfig): AxiosPromise
options(url: string, config?: AxiosRequestConfig): AxiosPromise
put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
}
Axios 中增加interceptors
import { AxiosRequestConfig, AxiosPromise, Method, AxiosResponse, ResolveFn, RejectedFn, Interceptors } from "../types";
import dispatchRequest from "./dispatchRequest";
import InterceptorManager from "./interceptorManager";
interface PromiseChain {
resolved: ResolveFn | ((config: AxiosRequestConfig) => AxiosPromise)
rejected?: RejectedFn
}
export default class Axios {
interceptors: Interceptors
constructor() {
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}
request(url: string | AxiosRequestConfig, config?: AxiosRequestConfig): AxiosPromise {
// 这里要定义一个新的newConfig变量,是因为AxiosPromise接收的泛型类型T是any,与config的类型不一致
let newConfig: any
newConfig = config || {}
if (typeof url === 'string') {
newConfig.url = url
} else {
newConfig = 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(newConfig)
while(chain.length) {
const { resolved, rejected } = chain.shift()!
promise = promise.then(resolved, rejected)
}
return promise
// return dispatchRequest(config)
}
get(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethod(url, 'get', config)
}
delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
// const newConfig = Object.assign(config || {}, {
// url,
// method: 'delete'
// })
// return dispatchRequest(newConfig)
return this._requestMethod(url, 'delete', config)
}
head(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethod(url, 'head', config)
}
options(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethod(url, 'options', config)
}
put(url: string, data?: any, config?: AxiosRequestConfig,): AxiosPromise {
return this._requestMethod(url, 'put', config, data)
}
post(url: string, data?: any, config?: AxiosRequestConfig,): AxiosPromise {
return this._requestMethod(url, 'post', config, data)
}
patch(url: string, data?: any, config?: AxiosRequestConfig,): AxiosPromise {
return this._requestMethod(url, 'patch', config, data)
}
_requestMethod(url: string, method: Method, config?: AxiosRequestConfig, data?: any) {
const newConfig = Object.assign(config || {}, {
url,
method,
data: data || null
})
return this.request(newConfig)
}
}
这样,axios就拥有了interceptors属性,interceptors属性拥有use和eject方法,拦截器添加之后,每次请求时,都会经过请求拦截器、请求、响应拦截器。至此,我们完成了拦截器的功能,实际工作中,我们可以利用它做一下登陆认证之类的工作。
我们目前通过ts-axios发送请求,往往会传入一堆配置,但是我们也希望ts-axios本身也会有一些默认配置,我们把用户传入的自定义配置和默认配置做一层合并。接下来我们来实现这个feature。