Skip to content

请求拦截器

参考 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
      // ... 自定义
    }
  })
}
// 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)
}
// 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
// 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
// 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
// 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
// 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
// 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