我正在参加「掘金·启航计划」
通过前几个章节,我们实现了axios的基础功能。在此基础上,对异常捕获的信息进行了增强。为了进一步提升用户体验,我们引入了混合类型,给axios拓展了多个语法糖。利用了重载的特性,实现了参数传参的不同类型。接下来,我们优化响应数据的类型推断和实现拦截器的功能。
需求分析
响应数据和请求支持泛型
- 我们希望接口返回的数据是我们所期望的数据类型,因此,一开始发送请求的时候,就传入期望的数据格式,以便更好地处理数据。
- 我们希望有以下格式的生成:
import axios, { AxiosResponse } from 'axios';
async function fetch<T>(url: string): Promise<AxiosResponse<T>> {
const response = await axios.get<T>(url);
return response;
}
interface User {
name: string;
age: number;
}
const fetchUser = async (): Promise<User> => {
const response = await fetch<User>('/api/user/1');
return response.data;
}
(async () => {
const user = await fetchUser();
console.log(user.name, user.age);
})();
这样声明后,我们可以很方便的处理响应数据,借助TS
能够推断出数据的类型。
首先,我们看下源码中是怎么实现这部分的:
github.com/axios/axios…
我们修改之前定义的类型,增加泛型
// types/index
export interface AxiosResponse<T = any> {
data: T
status: number
statusText: string
headers: any
config: AxiosRequestConfig
request: any
}
export interface AxiosPromise<T = any> extends Promise<AxiosResponse<T>> {
}
export interface Axios {
request<T = any>(config: AxiosRequestConfig): AxiosPromise<T>
get<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
delete<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
head<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
options<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>
}
export interface AxiosInstance extends Axios {
<T = any>(config: AxiosRequestConfig): AxiosPromise<T>
<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>
}
通过这一步,当我们发送请求的时候,得到的是AxiosPromise<T>
,然后可以在响应的数据中获取到T
。
响应拦截器和请求拦截器
通过阅读文档,我们大概知道需要的功能
- 基于
Promise
调用,支持then
,catch
- 有一个变量存储
interceptors
Interceptors
的request
或response
中有两个函数- use(添加)
- eject (删除)
我们先来拆分问题,先定义Interceptors.request
的接口类型
// types/index.ts
export interface AxiosInterceptorManager<T> {
// 创建成功后返回id作为删除的索引值
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
}
具体实现:
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
}
}
}
在原有的基础加入interceptors
// core/axios.ts
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>()
}
}
// 省略...
}
实现链式调用,我们利用的是Promise
的特性。
根据原有的axios
库,我们知道,请求拦截器是先进后出,响应拦截是先进先出。
我们使用一个队列来管理。
// core/axios.ts
interface PromiseChain<T> {
resolved: ResolvedFn<T> | ((config: AxiosRequestConfig) => AxiosPromise)
rejected?: RejectedFn
}
export default class Axios {
// 省略...
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)
})
// 通过 Promise 链式调用的方式,依次执行请求拦截器和请求方法
let promise = Promise.resolve(config)
while (chain.length) {
const { resolved, rejected } = chain.shift()!
promise = promise.then(resolved, rejected)
}
return promise
}
}
测试用例
import axios from '../../src/index'
axios.interceptors.request.use(config => {
console.log('first request interceptor')
config.headers.test += '1'
return config
})
axios.interceptors.request.use(config => {
console.log('second request interceptor')
config.headers.test += '2'
return config
})
axios.interceptors.request.use(config => {
console.log('third request interceptor')
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: {
axiosRequestInterceptor: ''
}
}).then(res => {
console.log(res.data)
})
对应的输出结果:
可以看到,请求拦截器是先进后出的,响应拦截器是先进先出,经典的洋葱圈模型。通过执行eject
,我们也成功的验证了删除函数。
总结
通过上述的章节,我们增加了响应数据的泛型支持,并实现了请求拦截器和响应拦截器,方便用户可以更好的对请求做处理(权限校验等功能)。通过实现这些功能,我们了解了洋葱圈模型是怎么样实现的,怎么将每一层链接起来,依次执行。