axios * 管理类链式调用手写实现及原理剖析
作者:小p 发布时间:2023-07-02 16:38:23
axios库的 * 使用
我们知道axios
库的 * 的使用方式如下:
// 添加一个请求 *
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);
});
在 axios
对象上有一个 interceptors
对象属性,该属性又有 request
和 response
2 个属性,它们都有一个 use
方法,use
方法支持 2 个参数,第一个参数类似 Promise.then
的 resolve
函数,第二个参数类似 Promise.then
的 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
类型的。
根据上述分析,我们先来定义一下 * 管理对象对外的接口。
接口定义
这里我们定义了 AxiosInterceptorManager
泛型接口,因为对于 resolve
函数的参数,请求 * 和响应 * 是不同的。
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
}
代码实现
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
}
// 遍历用户写的 * ,并执行fn函数把 * 作为参数传入
forEach(fn: (interceptor: Interceptor<T>) => void): void {
this.interceptors.forEach(interceptor => {
if (interceptor !== null) {
fn(interceptor)
}
})
}
eject(id: number): void {
if (this.interceptors[id]) {
// 置为null,不能直接删除
this.interceptors[id] = null
}
}
}
我们定义了一个 InterceptorManager
泛型类,内部维护了一个私有属性 interceptors
,它是一个数组,用来存储 * 。该类还对外提供了 3 个方法,其中 use
接口就是添加 * 到 interceptors
中,并返回一个 id
用于删除;
forEach
接口就是遍历 interceptors
用的,它支持传入一个函数,遍历过程中会调用该函数,并把每一个 interceptor
作为该函数的参数传入;eject
就是删除一个 * ,通过传入 * 的 id
删除。
链式调用实现
当我们实现好 * 管理类,接下来就是在 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>()
}
}
}
Interceptors
类型拥有 2 个属性,一个请求 * 管理类实例,一个是响应 * 管理类实例。我们在实例化 Axios
类的时候,在它的构造器去初始化这个 interceptors
实例属性。
接下来,我们修改 request
方法的逻辑,添加 * 链式调用的逻辑:
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
}]
// 把用户定义的请求 * 存放到任务链中,请求 * 最后注册的最先执行,所以使用unshift方法
this.interceptors.request.forEach(interceptor => {
chain.unshift(interceptor)
})
// 把响应 * 存放到任务链中
this.interceptors.response.forEach(interceptor => {
chain.push(interceptor)
})
// 利用config初始化一个promise
let promise = Promise.resolve(config)
// 遍历任务链
while (chain.length) {
// 取出任务链的首个任务
const { resolved, rejected } = chain.shift()!
// resolved的执行时机是就是上一个promise执行resolve()的时候,这样就形成了链式调用
promise = promise.then(resolved, rejected)
}
return promise
}
首先,构造一个 PromiseChain
类型的数组 chain
,并把 dispatchRequest
函数赋值给 resolved
属性;接着先遍历请求 * 插入到 chain
的前面;然后再遍历响应 * 插入到 chain
后面。
接下来定义一个已经 resolve 的 promise
,循环这个 chain
,拿到每个 * 对象,把它们的 resolved
函数和 rejected
函数添加到 promise.then
的参数中,这样就相当于通过 Promise 的链式调用方式,实现了 * 一层层的链式调用的效果。
注意我们 * 的执行顺序,对于请求 * ,先执行后添加的,再执行先添加的;而对于响应 * ,先执行先添加的,后执行后添加的。
来源:https://juejin.cn/post/7136065256941420552
猜你喜欢
- 很多人说设计是力求细节的,在网页设计里表达出的细节就是图标。图标在一个设计里带来了额外的注解并且使设计里的对象和元素引起用户的注意。以下介绍
- MySQL批量插入数据脚本#!/bin/bashi=1;MAX_INSERT_ROW_COUNT=$1;while [ $i -le $MA
- 有助于效率的类型选择1、使你的数据尽可能小最基本的优化之一是使你的数据(和索引)在磁盘上(并且在内存中)占据的空间尽可能小。这能给出巨大的改
- 1.要求数据库存储通讯录,要求按姓名/电话号码查询,查询条件只有一个输入入口,自动识别输入的是姓名还是号码,允许模糊查询。2.实现功能可通过
- 对于web开来说,用户登陆、注册、文件上传等是最基础的功能,针对不同的web框架,相关的文章非常多,但搜索之后发现大多都不具有完整性,对于想
- JetBrains针对学生推出了免费使用资格,但是很多同学却不知道或者说不知道怎样获得免费资格,只能千辛万苦的去寻找破解密钥,但现在JetB
- 如下所示:<?php$dir = dirname(__FILE__);$open_dir = opendir($dir);echo &
- 也许你听说过Hibernate的大名,但可能一直不了解它,也许你一直渴望使用它进行开发,那么本文正是你所需要的!在本文中,我向大家重点介绍H
- mysql最常用的索引结构是btree(O(log(n))),但是总有一些情况下我们为了更好的性能希望能使用别的类型的索引。hash就是其中
- 一、利用webbrowser.open()打开一个网站:>>> import webbrowser >>>
- 1.算法:对于一组关键字{K1,K2,…,Kn}, 首先从K1,K2,…,Kn中选择最小值,假如它是 Kz,则将Kz与 K1对换;然后从K2
- 第一次写ASP类,实现功能:分段统计程序执行时间,输出统计表等.程序代码:Class ccClsProcessTimeRecord
- 一年中秋至 又见圆月时导语假设农历八月十五,程序员错过了今年的中秋圆月。▼程序员的苦只有他们寄几知道bug,bug,bug,bug,bug,
- 将套接字流重定向到标准输入或输出流#!/usr/bin/env python3"""测试socket-stre
- 前言Hi! 这是随笔专栏的第一篇文章。好的开始等于成功了一半。在之后的日子里,除了不定期分享实战中可总结出的小项目外,还会经常与大分享开发时
- 一、两种模式pytorch可以给我们提供两种方式来切换训练和评估(推断)的模式,分别是:model.train() 和 model.eval
- 实例如下所示:tcode={}transcode={}def GetTcode():#从文本中获取英文对应的故障码,并保存在tcode字典(
- 由于不同的浏览器,比如Internet Explorer 6,Internet Explorer 7,Mozilla Firefox等,对C
- Linux 自动备份oracle数据:曾经有个同事,来回操作开发和生产的数据库,结果误删了生产的数据库,那种心情我想不是一般人能理解的,虽然
- 请问如何在Oracle Setver端检测ODBC是否连接好了?首先,在SQLPLUS安装时勾选oracle open client ada