All files / src methods.ts

95.83% Statements 46/48
100% Branches 15/15
85.71% Functions 6/7
95.55% Lines 43/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 1311x   1x 1x 1x 1x                       1x 1x             1062x             224x 224x 224x   224x               224x     224x 224x 12x       212x 212x   14x         198x 12x   12x       198x   84x 84x   84x 42x       36x   36x           84x 6x       3x 3x           114x       114x 42x                         198x 198x 198x   114x     198x   198x   198x      
import { normalizeConfig } from './helpers'
import type { AjaxInstance, AjaxOption, AxiosMethods } from './types'
import { addCalledCanceller, addCanceller, deleteCancellerByHash } from './canceller'
import { generateRequestHash } from './utils'
import { addPendingRequest, checkIfHasPendingRequest, clearPendingRequest, getCachedRequest } from './duplicateRequestPrevent'
import { addRequestCanceler, clearCancelerByHash, execCanceler } from './cancelLastSameRequest'
 
// GDataShape通过initAjax的第二个泛型参数传入,单独的请求如果不传返回数据的类型,则默认使用GDataShape
// DataType通过ajax.get, ajax.post等方法的泛型参数传入,如果定义了这个参数,会将GDataShape覆盖
export interface RequestCreator<COPT extends object, GDataShape extends object = any> {
  // request = ajax.get(url, options_1)
  <DataType = GDataShape>(url: string, options?: Partial<AjaxOption & COPT>): {
    // request(params, options_2)
    (params?: object | null, opts?: Partial<AjaxOption & COPT>): Promise<DataType>
  }
}
 
const postLikeMethods = ['PUT', 'POST', 'DELETE' , 'PATCH']
export const generateMethod = <COPT extends NonNullable<unknown>, GDataShape extends object = any>(ajax: AjaxInstance<COPT>, method: AxiosMethods): RequestCreator<COPT, GDataShape> =>
  /**
   *
   * @param url 完整的请求url链接
   * @param options ajax配置
   * @returns 返回一个发送请求的函数
   */
  <DataType = GDataShape>(url: string, options?: Partial<AjaxOption & COPT>) =>
    /**
     * 发送请求
     * @param params get类请求的url参数 or post类请求的请求体数据
     * @param opts ajax参数,拥有最高优先级
     * @returns 请求数据
     */
    async (params?: object | null, opts?: Partial<AjaxOption & COPT>) => {
      const mergedOptions = { ...options, ...opts }
      const isPostLikeMethod =  postLikeMethods.includes(method)
 
      const config = {
        ...mergedOptions,
        params: isPostLikeMethod ? undefined : params,
        data: isPostLikeMethod ? params : undefined,
        url,
        method
      } as any
 
      const finalOpts = normalizeConfig(ajax, config)
 
      // mock
      const mock = finalOpts.mock
      if (mock) {
        return Promise.resolve(mock)
      }
 
      // 完全相同的请求,且都处于pending状态,复用上一个
      const hashKey = generateRequestHash(finalOpts)
      if (checkIfHasPendingRequest(hashKey)) {
        // 重复,且处于pending状态的请求,复用
        return getCachedRequest(hashKey)
      }
 
      // 这个检查应该位于前面复用请求的逻辑后面,不能乱序
      // 如果需要取消之前处于pending状态相同url请求
      if (finalOpts.cancelLastSameUrl) {
        const currUrl = finalOpts.url
        // 执行所有缓存的url相关的取消函数
        execCanceler(currUrl)
      }
 
      // 缓存当前请求的取消函数
      if (typeof AbortController !== 'undefined') {
        // 环境支持AbortController
        const controller = new AbortController()
        finalOpts.signal = controller.signal
 
        if (typeof mergedOptions.cancelKey !== 'undefined') {
          addCanceller(
            mergedOptions.cancelKey,
            hashKey,
            () => {
              controller.abort.call(controller)
              // 删除挂载在ajax上的请求中断函数
              deleteCancellerByHash(mergedOptions.cancelKey as string, hashKey)
            }
          )
        }
 
        // 缓存当前请求的取消函数,假如后续有相同的url请求需要取消该请求,可以通过这里缓存的函数实现(只针对get请求)
        if (finalOpts.cancelLastSameUrl && finalOpts.method.toLocaleLowerCase() === 'get') {
          addRequestCanceler(
            finalOpts.url,
            hashKey,
            () => {
              controller.abort.call(controller)
              clearCancelerByHash(finalOpts.url, hashKey)
            }
          )
        }
      } else {
        // 环境如果不支持AbortController,取消函数挂载到ajax只能交给adapter里面去实现了
        finalOpts.signal = undefined
        // 因为异步的关系(axios中发出请求之前,对拦截函数处理可能是异步的),逻辑可能还没走到adapter中去,取消函数就已经被调用了
        // 此时取消函数还是undefined,所以这里提供默认取消函数
 
        if (typeof mergedOptions.cancelKey !== 'undefined') {
          addCanceller(
            mergedOptions.cancelKey,
            hashKey,
            () => {
              // 默认函数将已经调用的取消函数名称记录下来,在adapter中走到发送请求的逻辑时,直接中断即可
              addCalledCanceller(mergedOptions.cancelKey as string, hashKey, true)
              deleteCancellerByHash(mergedOptions.cancelKey as string, hashKey)
            }
          )
        }
      }
 
      try {
        const request = ajax.request<any, DataType>(finalOpts)
        addPendingRequest(hashKey, request, finalOpts.reUseDuration)
        return await request
      } catch (e) {
        return Promise.reject(e)
      } finally {
        // 请求成功,删除ajax.canceller上对应的取消函数
        deleteCancellerByHash(finalOpts.cancelKey, hashKey)
        // 删除用于取消相同URL请求功能而缓存的取消函数
        clearCancelerByHash(finalOpts.url, hashKey)
        // 删除缓存的请求(用于复用finalOpts.reUseDuration内完全相同的多个请求)
        clearPendingRequest(hashKey)
      }
    }