查看axios的源码,会发现拦截器是由简单数组实现,挂载use
eject
方法。拦截器注册的Id实际就是数组的编号,实现的核心在request调用前的拦截器队列编排上。满足平常开发,如果想做扩展就有些限制,所以这里希望通过提供一个自定义的拦截器中间层。提供些扩展功能。
目标
- 自定义标识id
- 自定义请求端拦截器筛选
- 执行顺序与绑定顺序一致
- 链式调用
使用
import { CandyPaper } from './src/core'
import { Interceptor } from './src/interceptor'
const http = new CandyPaper()
const task = {
key: 'taskId',
fulfilled: (ctx: AxiosRequestConfig) => {...},
rejected: (ctx: AxiosResponse) => {...},
runWhen: (ctx: AxiosRequestConfig) => true
}
// 注册拦截器
http.interceptor.request
// 模式一
.use(
task.fulfilled
)
// 模式二
.use(
task.fulfilled,
task.rejected
)
// 模式三
.use(
task.key,
taks.fulfilled,
taks.rejected
)
// 模式四
.use( task )
// 注销拦截器
http.interceptor.request
// 模式一
.eject(
task.key
)
// 模式二
.eject(
task.fulfilled
)
// 拦截器筛选
http.request(
url: '/',
// 忽略 'task' 拦截器
$intercepteFilter: interceptor.Interceptor.excluedByKeys([
task.key
])
)
http.request(
url: '/',
// 只执行 'task' 拦截器
$intercepteFilter: interceptor.Interceptor.incluedByKeys([
task.key
])
)
实现
缓存队列
因为需要自定义标识,同时需要保存注册顺序。 这里通过扩展Map类型实现
/**
* map 扩展
* @props list 键队列
* @fn $set 设置新值, 并缓存键
* @fn $get 获取值, 可接收key数组
* @fn $delete 删除值, 并移除键
* @fn $map 遍历键
* @fn $clear 清空
*/
export class Lmap extends Map{
// key 添加记录
list: K[] = []
constructor(entries?: readonly (readonly [K, V])[] | null){
super(entries)
entries && entries.map(([key]) => this.list.push(key))
}
$set(key: K, value: V): this {
super.set(key, value)
this.list.push(key)
return this
}
$delete(key: K): boolean {
if(!this.has(key)){
return true
}
const status = super.delete(key)
if(status){
this.list = this.list.filter(k => k != key)
}
return status
}
$clear(){
this.clear()
this.list = []
}
$get(keys: K): V
$get(keys: K[]): V[]
$get(keys: K | K[]): (undefined | V | V[]) {
if(!keys){
return
}
if(!Array.isArray(keys)){
return this.get(keys)
}
return keys.map(k => this.get(k)).filter(i => !!i) as V[]
}
$map(cb:(k: K) => T): T[]
$map(cb:(k: K, v?: V) => T):T[]
$map(cb: (k: K, v?:V) => T): T[]{
return this.list.map(key => cb(key, this.get(key)))
}
}
扩展aixos类型定义
// ./types/shime.aixos.d
import { AxiosRequestConfig, AxiosInterceptorManager } from 'axios'
declare module 'axios'{
export interface AxiosRequestConfig{
// 扩展请求配置参数
$intercepteFilter?: (keys: IndexKey[]) => IndexKey[]
}
// 任务拦截
interface Fulfilled{
(d: T): T | Promise
// 自定义标识
key?: IndexKey
}
// 错误拦截
interface Rejected{
(err: any): any
key?: IndexKey
}
// 拦截器端,筛选函数
// @types/aixos未做定义,需要自己补充
export type RunWhen = (conf:AxiosRequestConfig) => boolean | null
// 拦截器项定义
export interface InterceptorHandler{
key?: IndexKey
fulfilled: Fulfilled
rejected?: Rejected
runWhen?: RunWhen
}
// 扩展拦截器定义
// @types/aixos未做定义,需要自己补充
export interface AxiosInterceptorManager{
handlers: InterceptorHandler[]
}
}
拦截器中间层
import {
AxiosRequestConfig,
InterceptorHandler,
Fulfilled,
Rejected,
RunWhen
} from 'axios'
import { Lmap } from './utils'
import { is } from 'ramda'
interface InterceptorItem extends InterceptorHandler {
key: IndexKey
}
export interface InterceptorOptions {
$intercepteFilter?: (keys: IndexKey[]) => IndexKey[]
}
/**
* 拦截器
*/
export class Interceptor{
// 拦截器队列
handlers: Lmap> = new Lmap()
// 默认拦截器id生成器
createDefKey(){
return new Date().getTime() + this.handlers.list.length
}
/**
* 注册拦截器
* @param key 标识符
* @param onFulfilled 任务函数
* @param onRejected 错误捕获
* @exmple
* 模式一
* use('token', setToken)
*
* 模式二
* use(setToken, onError)
*
* 模式三
* use('token', setToken, onError)
*
* 模式四
* use({
* key: 'token',
* onFulfilled: setToken,
* onRejected: onError
* runWhen: (ctx: AxiosRequestConfig) => {...}
* })
*/
use(key: Fulfilled):Interceptor
use(key: InterceptorItem): Interceptor
use(key: IndexKey, onFulfilled: Fulfilled): Interceptor
use(key: Fulfilled, onFulfilled: Rejected): Interceptor
use(key: IndexKey, onFulfilled: Fulfilled, onRejected: Rejected): Interceptor
use(key?: IndexKey | Fulfilled | InterceptorItem, onFulfilled?: Fulfilled, onRejected?: Rejected ){
let runWhen: RunWhen | undefined
if(!key){
return this
}
if(is(Function, key)){
onRejected = onFulfilled
onFulfilled = key
key = onFulfilled.key || this.createDefKey()
}
if(is(Object, key)){
const options = key as InterceptorItem
key = options.key
onFulfilled = options.fulfilled
onRejected = options.rejected
runWhen = options.runWhen
}
if(this.handlers.has(key)){
throw new Error(`拦截器已注册: ${String(key)}`)
}
if(onFulfilled){
this.handlers.$set(key, {
key,
fulfilled: onFulfilled,
rejected: onRejected,
runWhen
})
onFulfilled.key = key
}
return this
}
/**
* 注销拦截器
* @param key 注册标识或注册函数
* @example
* 模式一
* const task = () => {}
* interceptor.use(
* task
* )
* interceptor.eject(task)
*
* 模式二
* interceptor.use(
* 'taskId',
* task
* )
* interceptor.eject(task)
* or
* interceptor.eject('taskId')
*/
eject(key: IndexKey):void
eject(key: Fulfilled):void
eject(key: IndexKey | Fulfilled){
let _k: IndexKey | undefined
if(is(Function, key)){
_k = (key as Fulfilled).key
}else{
_k = key
}
_k && this.handlers.$delete(_k)
}
// 清空拦截器队列
clean(){
this.handlers.$clear()
}
// 构建任务队列
queupUp(ctx: InterceptorOptions){
// 如果拦截器筛选为空, 则应用所有已注册拦截器
const filter = ctx.$intercepteFilter ? ctx.$intercepteFilter : (keys: IndexKey[]) => keys
// 筛选可用队列
return filter(this.handlers.list).reduce((acc, next) => {
const handler = this.handlers.get(next)
return handler ? [
...acc,
{
...handler
}
] : acc
}, [] as InterceptorHandler[])
}
// 预设排除筛选
static excluedByKeys(exclueds: IndexKey[]){
return (handlersKeys: IndexKey[]) => handlersKeys.filter(key => !exclueds.includes(key))
}
// 预设包含筛选
static incluedByKeys(inclueds: IndexKey[]){
return (handlersKeys: IndexKey[]) => handlersKeys.filter(key => inclueds.includes(key))
}
}
请求包装
import axios from 'axios'
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios'
import { Interceptor, InterceptorOptions } from './interceptor'
import { is } from 'ramda'
export class CandyPaper{
candy: AxiosInstance
interceptor = {
request: new Interceptor(),
response: new Interceptor()
}
/**
* @param config axios实例 or axios配置对象
* @example
* 1. new CandyPaper({ baseUrl: '/' })
* 2. new CandyPaper(axios.create({baseUrl: '/'}))
*/
constructor(config?: AxiosInstance)
constructor(config?: AxiosRequestConfig)
constructor(config?: AxiosRequestConfig | AxiosInstance){
this.candy = is(Function, config) ? config : axios.create(config)
}
// 重组请求拦截器列表
protected resetInterceptors(ctx: InterceptorOptions){
const interceptorRequests = this.candy.interceptors.request.handlers.filter(i => !i.key)
const interceptorResponses = this.candy.interceptors.response.handlers.filter(i => !i.key)
const middleRequests = this.interceptor.request.queupUp(ctx).sort(() => -1)
this.candy.interceptors.request.handlers = [
...interceptorRequests,
...middleRequests
].sort(() => -1) // 反序, 保证执行顺序与注册顺序一致
const middleResponses = this.interceptor.response.queupUp(ctx)
this.candy.interceptors.response.handlers = [
...interceptorResponses,
...middleResponses
]
}
request(options: AxiosRequestConfig){
// 请求前重置拦截器队列
this.resetInterceptors(options)
return this.candy(options)
}
}
参考
axios 如何设计拦截器