请求拦截器
参考 InterceptorManager
& Axios Instance
源码实现
实现功能
- 请求拦截器
- 批量取消请求
- 多请求共用
loading
API 文件方式
ts
// src/api
import { fetch } from '~/plugins/request/index'
const prefix = '/demo'
export function demo(demo: string) {
return fetch({
url: `${prefix}/${demo}`,
method: 'GET',
meta: {
load: false
// ... 自定义
}
})
}
请求响应结构声明
ts
// src/plugins/request/index.ts
import service from './request'
import { RequestConfig } from './types/index'
interface ResultData<TData> {
code: number
msg: string
data: TData
rid: string
}
export function fetch<V = AnyObject>(config: RequestConfig): Promise<ResultData<V>> {
return service.request(config)
}
拦截器使用方式
ts
// src/plugins/request.ts
import RequestInstance from './core/RequestInstance'
import { defaultConfig } from './config'
import CancelToken from './core/CancelToken'
let app: WechatMiniprogram.App.Instance<IAppOption>
let source = CancelToken.source()
defaultConfig.cancelToken = source.token
let needLoadingRequestCount = 0
export function showLoading() {
if (needLoadingRequestCount === 0) {
// 打开加载动画
wx.showLoading({
title: '加载中',
mask: true
})
}
needLoadingRequestCount++
}
export function tryHideLoading() {
if (needLoadingRequestCount <= 0)
return
needLoadingRequestCount--
if (needLoadingRequestCount === 0) {
wx.hideLoading()
source = CancelToken.source()
defaultConfig.cancelToken = source.token
}
}
// 可以new多个request来支持多个域名请求
const service = new RequestInstance(defaultConfig)
service.interceptors.request.use(
(config) => {
if (!app)
app = getApp<IAppOption>()
if (config.meta?.load)
showLoading()
config.header!.Authorization = app.globalData.token
// return false 表示请求拦截,不会继续请求
return config
},
(err) => {
console.error(err)
return Promise.reject(err)
}
)
// 请求结束
service.interceptors.response.use(
(response) => {
tryHideLoading()
return response.data
},
(err) => {
source.cancel?.('认证过期!')
if (err.code === 403)
app.globalData.token = ''
tryHideLoading()
app.error(err.message)
return Promise.reject(err)
}
)
export default service
请求实例
ts
// src/plugins/request/core/RequestInstance.ts
import type { ConfigInstance, RequestConfig } from '../types/index'
import { mergeConfig } from './util'
import InterceptorManager from './InterceptorManager'
import dispatchRequest from './dispatchRequest'
class RequestInstance {
// 默认参数
defaults: ConfigInstance
interceptors = {
request: new InterceptorManager<RequestConfig>(),
response: new InterceptorManager<WechatMiniprogram.RequestSuccessCallbackResult<any>>(),
}
constructor(config: ConfigInstance) {
this.defaults = config
}
// 接口请求方法
request(config: RequestConfig) {
// 数据合并
const requestInfo = mergeConfig(this.defaults, config)
// 过滤掉跳过的拦截器
const requestInterceptorChain: any[] = []
let synchronousRequestInterceptors = true
this.interceptors.request.forEach((interceptor) => {
if (
typeof interceptor.runWhen === 'function'
&& interceptor.runWhen(requestInfo) === false
)
return
synchronousRequestInterceptors
= synchronousRequestInterceptors && interceptor.synchronous
requestInterceptorChain.unshift(
interceptor.fulfilled,
interceptor.rejected
)
})
const responseInterceptorChain: any[] = []
this.interceptors.response.forEach((interceptor) => {
responseInterceptorChain.push(
interceptor.fulfilled,
interceptor.rejected
)
})
let promise: Promise<any>
if (!synchronousRequestInterceptors) {
let chain: any[] = [dispatchRequest, undefined]
chain.unshift(...requestInterceptorChain)
chain = chain.concat(responseInterceptorChain)
promise = Promise.resolve(requestInfo)
while (chain.length)
promise = promise.then<any, any>(chain.shift(), chain.shift())
return promise
}
let newConfig = requestInfo
while (requestInterceptorChain.length) {
const onFulfilled = requestInterceptorChain.shift()
const onRejected = requestInterceptorChain.shift()
try {
newConfig = onFulfilled?.(newConfig)
}
catch (error) {
onRejected?.(error)
break
}
}
try {
promise = dispatchRequest(newConfig)
}
catch (error) {
return Promise.reject(error)
}
while (responseInterceptorChain.length) {
promise = promise.then<any, any>(
responseInterceptorChain.shift(),
responseInterceptorChain.shift()
)
}
return promise
}
}
export default RequestInstance
分派请求
ts
// src/plugins/request/core/dispatchRequest.ts
import { RequestConfig } from '../types/index'
import { SheepError } from './SheepError'
// 请求
export function dispatchRequest(config: RequestConfig) {
return new Promise<WechatMiniprogram.RequestSuccessCallbackResult>((resolve, reject) => {
let onCanceled: (cancele: string) => void
let request: any = wx.request({
...config,
dataType: 'json',
success(res) {
console.log(
config.url,
'请求参数: ',
config.params,
'返回数据: ',
res.data
)
const data = res.data as AnyObject
if (res.statusCode === 200 && (config.validateStatus?.(data.code) || data.code === 0))
return resolve(res)
return reject(new SheepError(res.data as string, res.statusCode))
},
fail(err) {
return reject(new SheepError(err.errMsg, err.errno))
},
complete() {
config.cancelToken?.unsubscribe(onCanceled)
}
})
if (config.cancelToken) {
onCanceled = (cancel) => {
if (!request)
return
request.abort()
request = null
reject(cancel)
console.log('取消请求', request, config.url)
}
config.cancelToken.subscribe(onCanceled)
}
})
}
export default dispatchRequest
拦截器管理器
ts
// src/plugins/request/core/InterceptorManager.ts
import { RequestConfig } from '../types/index'
import { SheepError } from './SheepError'
import { customForEach } from './util'
interface Handler<T> {
fulfilled: (config: T) => T
rejected?: (err: SheepError) => void
synchronous: boolean
runWhen: any
}
interface HandlerOptions {
synchronous: boolean
runWhen: (config: RequestConfig) => boolean
}
class InterceptorManager<V> {
public handlers: any[] = []
use<T = V>(
fulfilled: (config: T) => T | Promise<V>,
rejected?: (err: SheepError) => void,
options?: HandlerOptions
) {
this.handlers.push({
fulfilled,
rejected,
synchronous: options?.synchronous ?? false,
runWhen: options?.runWhen ?? null
})
return this.handlers.length - 1
}
eject(id: number) {
if (this.handlers[id])
(this.handlers[id] as any) = null
}
forEach(fn: (v: any) => void) {
customForEach(this.handlers, (h: any) => {
if (h !== null)
fn(h)
})
}
}
export default InterceptorManager
取消请求
ts
// src/plugins/request/core/CancelToken.ts
type Callback = (val: string) => void
export class CancelToken {
public promise: Promise<string>
public reason: string | null = null
private _listeners: Function[] | null = null
constructor(executor: (callback: Callback) => void) {
let resolvePromise: (value: string) => void
this.promise = new Promise<string>((resolve) => {
resolvePromise = resolve
})
this.promise.then((cancel) => {
if (!this._listeners)
return
let i = this._listeners.length
while (i-- > 0)
this._listeners[i](cancel)
this._listeners = null
})
executor((message) => {
if (this.reason) {
// 已申请取消
return
}
this.reason = message
resolvePromise(message)
})
}
/**
* 订阅取消信号
*/
subscribe(listener: (value: string) => void) {
if (this.reason) {
listener(this.reason)
return
}
if (this._listeners)
this._listeners.push(listener)
else
this._listeners = [listener]
}
/**
* 取消订阅取消信号
*/
unsubscribe(listener: (value: string) => void) {
if (!this._listeners)
return
const index = this._listeners.indexOf(listener)
if (index !== -1)
this._listeners.splice(index, 1)
}
/**
* 返回一个对象,该对象包含一个新的“CancelToken”和一个函数,该函数在调用时会取消“CancelToken”。
*/
static source() {
let cancel: Callback | undefined
const token = new CancelToken((c) => {
cancel = c
})
return {
token,
cancel
}
}
}
export default CancelToken