查看axios的源碼,會發(fā)現(xiàn)攔截器是由簡單數(shù)組實現(xiàn),掛載use eject 方法。攔截器注冊的Id實際就是數(shù)組的編號,實現(xiàn)的核心在request調用前的攔截器隊列編排上。滿足平常開發(fā),如果想做擴展就有些限制,所以這里希望通過提供一個自定義的攔截器中間層。提供些擴展功能。
目標
- 自定義標識id
- 自定義請求端攔截器篩選
- 執(zhí)行順序與綁定順序一致
- 鏈式調用
使用
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: '/',
// 只執(zhí)行 'task' 攔截器
$intercepteFilter: interceptor.Interceptor.incluedByKeys([
task.key
])
)
實現(xiàn)
緩存隊列
因為需要自定義標識,同時需要保存注冊順序。 這里通過擴展Map類型實現(xiàn)
/**
* map 擴展
* @props list 鍵隊列
* @fn $set 設置新值, 并緩存鍵
* @fn $get 獲取值, 可接收key數(shù)組
* @fn $delete 刪除值, 并移除鍵
* @fn $map 遍歷鍵
* @fn $clear 清空
*/
export class Lmap<K, V> extends Map<K, V>{
// 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<T = any>(cb:(k: K) => T): T[]
$map<T = any>(cb:(k: K, v?: V) => T):T[]
$map<T = any>(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{
// 擴展請求配置參數(shù)
$intercepteFilter?: (keys: IndexKey[]) => IndexKey[]
}
// 任務攔截
interface Fulfilled<T = any>{
(d: T): T | Promise<T>
// 自定義標識
key?: IndexKey
}
// 錯誤攔截
interface Rejected{
(err: any): any
key?: IndexKey
}
// 攔截器端,篩選函數(shù)
// @types/aixos未做定義,需要自己補充
export type RunWhen = (conf:AxiosRequestConfig) => boolean | null
// 攔截器項定義
export interface InterceptorHandler<V>{
key?: IndexKey
fulfilled: Fulfilled<V>
rejected?: Rejected
runWhen?: RunWhen
}
// 擴展攔截器定義
// @types/aixos未做定義,需要自己補充
export interface AxiosInterceptorManager<V>{
handlers: InterceptorHandler<V>[]
}
}
攔截器中間層
import {
AxiosRequestConfig,
InterceptorHandler,
Fulfilled,
Rejected,
RunWhen
} from 'axios'
import { Lmap } from './utils'
import { is } from 'ramda'
interface InterceptorItem <T = any> extends InterceptorHandler <T> {
key: IndexKey
}
export interface InterceptorOptions {
$intercepteFilter?: (keys: IndexKey[]) => IndexKey[]
}
/**
* 攔截器
*/
export class Interceptor<T = AxiosRequestConfig>{
// 攔截器隊列
handlers: Lmap<IndexKey, InterceptorItem<T>> = new Lmap()
// 默認攔截器id生成器
createDefKey(){
return new Date().getTime() + this.handlers.list.length
}
/**
* 注冊攔截器
* @param key 標識符
* @param onFulfilled 任務函數(shù)
* @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<T>):Interceptor<T>
use(key: InterceptorItem<T>): Interceptor<T>
use(key: IndexKey, onFulfilled: Fulfilled<T>): Interceptor<T>
use(key: Fulfilled<T>, onFulfilled: Rejected): Interceptor<T>
use(key: IndexKey, onFulfilled: Fulfilled<T>, onRejected: Rejected): Interceptor<T>
use(key?: IndexKey | Fulfilled<T> | InterceptorItem<T>, 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<T>
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 注冊標識或注冊函數(shù)
* @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<T>[])
}
// 預設排除篩選
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<AxiosRequestConfig>(),
response: new Interceptor<AxiosResponse>()
}
/**
* @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) // 反序, 保證執(zhí)行順序與注冊順序一致
const middleResponses = this.interceptor.response.queupUp(ctx)
this.candy.interceptors.response.handlers = [
...interceptorResponses,
...middleResponses
]
}
request(options: AxiosRequestConfig){
// 請求前重置攔截器隊列
this.resetInterceptors(options)
return this.candy(options)
}
}