上篇[axios 二次封装-拦截器队列, 这篇是基于拦截器队列实现的状态处理拦截器, 某些情况下我们需要针对不同的响应状态码,执行不同的处理函数。例如:
410 权限校验, 500 服务器错误等。除了常规的http状态码,后台也可能定了一套内部的请求码,例如: { code: 1, message:'OK' }。由此发现,如果希望通过定义一套处理模板代码,是无法满足实际业务需求的。
这里的处理方式是,拦截器只做基础的流程控制以及接口数据统一。将状态处理转为类似form规则校验的if(){doing}
的方式,交由使用方定义具体的处理规则。
这里将规则(rule
)分为三类:
行为(act
)可以简化为返回任意值的处理函数。
在编写具体处理逻辑前,需要针对rule
act
设计统一的上下文执行参数。借由统一的接口定义简化rule的定义方式。
export const enum CUM_CODE {
// 未知捕获
UNKNOWN = -1,
// 请求失败,未获取到 status
UNKNOWN_RES = -2
}
// 统一的上下文参数,将交由rule和act
export interface StatusCtx<T = any> {
config: AxiosRequestConfig; // 请求配置
data?: T // 响应数据
status: number // http状态码
response?: AxiosResponse // 请求体
request?: any // 响应体
}
// 正对不同模式的规则类型定义
export type StatusCode = number // 具体状态码
export type StatusCodeRange = [number, number] //状态区间
export interface StatusValidate<T = any> {
(ctx: StatusCtx<T>): boolean
} // 自定义状态处理
// 规则匹配
export interface StatusValidator{
(ctx: StatusCtx): boolean
}
// 规则行为
export interface StatusAct<T = any>{
(ctx: StatusCtx): T | Promise<T>
}
// 规则项
export interface StatusRule<T>{
if: StatusValidator // 规则匹配
act: StatusAct<T> // 匹配后的处理函数
}
export type RulesIndex = StatusCode | StatusCodeRange | StatusValidate
定义一个状态规则管理类,提供规则的管理方法和拦截器适配口。
class Status<T>{
// 因为rule可能为数组或函数,所以选择Map作为rule仓库
rules: Map<RulesIndex, StatusRule<T>> = new Map()
}
这里将三种规则模式统一为规则匹配函数
add(key:RulesIndex, act:StatusAct<T>){
/**
* 单一状态
* add(404)
*/
if (isNumberCode(key)){
this.rules.set(
key,
{
if: (ctx: StatusCtx) => ctx.status === key,
act,
}
)
// 链式调用
return this
}
/**
* 范围状态
* add([200, 300])
*/
if(isRangeCode(key)){
this.rules.set(
key,
{
if: (ctx: StatusCtx) => range(key[0], key[1])(ctx.status),
act,
}
)
return this
}
/**
* 自定义判断函数
* add((ctx: StatusCtx) => ctx.data.code === 200)
*/
if (isFnCode(key)){
this.rules.set(
key,
{
if: key,
act
}
)
return this
}
throw new Error(`状态规则必须为 Number, [Number, Number], (ctx: StatusCtx) => boolean`)
}
遍历规则列表,筛选匹配项
run(ctx: StatusCtx){
for (let i of this.rules.values()) {
if (i.if(ctx)) {
return i.act(ctx)
}
}
}
请求响应后,将转为resolve
或 reject
状态。需要对这两种情况做对应的适配,以捕获到所需的状态码。
/**
* 成功接收器
* @param ctx
*/
adapterRs(ctx: AxiosResponse){
const statusCtx: StatusCtx = {
data: ctx.data,
status: ctx.status,
config: ctx.config,
request: ctx.request,
response: ctx
}
const d = this.run(statusCtx)
return d || ctx
}
/**
* 错误接收器
* @param e
*/
adapterRj(e: any){
let statusCtx = {
config: {},
status: CUM_CODE.UNKNOWN
} as StatusCtx
if (isAxiosError(e)){
const { response, request } = e
statusCtx = {
config: e.config,
status: response?.status || CUM_CODE.UNKNOWN_RES,
request: request,
response: response,
data: response?.data
}
}
const d = this.run(statusCtx)
return d || Promise.reject(e)
}
// 移除规则
remove(key:RulesIndex){
if(this.rules.has(key)){
this.rules.delete(key)
}
return this
}
// 清空规则
clear(){
this.rules = new Map()
}
// 规则数
len(){
return [...this.rules.keys()].length
}
const status = new Status
// 绑定拦截器
axios.interceptors.response.use(
(ctx: AxiosResponse) => status.adapterRs(ctx),
(e: any) => status.adapterRj(e)
)
// 条件规则
status
.add(
400,
() => Promise.reject({message: '请求错误'})
)
.add(
410,
() => Promise.reject({message: '未登录或登录失效'})
)
对于不同的拦截器对象,这里定义了两个适配类,提供快速注册方法
// axios
export class StatusForAxios<T> extends Status<T>{
static create() {
return new StatusForAxios()
}
install(axios: AxiosInstance) {
return axios.interceptors.response.use(
(ctx: AxiosResponse) => this.adapterRs(ctx),
(e: any) => this.adapterRj(e)
)
}
}
// CandyPaper
export class StatusForCandyPaper<T> extends Status<T>{
installKey: string = DEF_STATUS_KEY
static create(){
return new StatusForCandyPaper()
}
install(candyPaper: CandyPaper) {
return candyPaper.interceptor.response.use(
this.installKey,
(ctx: AxiosResponse) => this.adapterRs(ctx) as any,
(e: any) => this.adapterRj(e)
)
}
}
const http = new CandyPaper({baseURL: 'https://www.baidu.com'})
const status = new Status()
// axios
status.install(http.candyPaper)
// candyPaper
http.use(status)
在之前的前置类型定义中,定义了 CUM_CODE
。通过 CUM_CODE.UNKNOWN 可以设置兜底捕获
status.add(
-1,
(ctx) => {...}
)