Skip to content

Latest commit

 

History

History
378 lines (274 loc) · 9.6 KB

request.md

File metadata and controls

378 lines (274 loc) · 9.6 KB
toc order
menu
4

统一请求函数

统一请求函数统一接收各个接口的配置,并返回具体接口的响应结果。

统一请求函数文件路径可通过 requestFunctionFilePath 配置,该文件必须导出一个统一请求函数。

如何编写

默认导出一个异步的请求函数即可:

import { RequestFunctionParams } from 'yapi-to-typescript'

export default async function request<TResponseData>(
  payload: RequestFunctionParams,
): Promise<TResponseData> {
  // ...
  // 基于 payload 获取接口信息,
  // 然后对接口发起请求,
  // 接着获取接口响应数据,
  // 并且根据 payload 的相关信息解析响应数据作为请求结果,
  // 最后返回请求结果。
  // ...
}

如何获取接口信息

统一请求函数内可通过第一个参数 payload 获取接口信息。

mockUrl

  • 类型:string
  • 说明:

接口 Mock 地址,该地址根据接口 ID 自动生成,如:https://my.yapi.server/mock/993

devUrl

  • 类型:string
  • 说明:

接口测试环境地址,该地址根据配置 devEnvName 拉取,如:https://my.dev.server/api/v2

prodUrl

  • 类型:string
  • 说明:

接口生产环境地址,该地址根据配置 prodEnvName 拉取,如:https://my.prod.server/api/v2

path

  • 类型:string
  • 说明:

接口路径,该路径以 YApi 上配置的接口路径为基准,首先将其中的路径参数替换为路径参数值,然后将接口的查询参数序列化为查询字符串附着在后面。举个例子:

对于上图所示的接口,如果你调用该接口请求函数时传参 { id: '120', site: 'weixin' },则 path 为:/getUserInfo/120?site=weixin

method

  • 类型:'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'PATCH'

    实际请这样使用:

    import { Method } from 'yapi-to-typescript'
    
    console.log(Method.GET, Method.POST)
  • 说明:

请求方法。

requestHeaders 3.19.0+

  • 类型:Record<string, string>

  • 说明:

接口请求头,来自这里:

其中 Content-Type 会被去除。

requestBodyType

  • 类型:'query' | 'form' | 'json' | 'text' | 'file' | 'raw' | 'none'

    实际请这样使用:

    import { RequestBodyType } from 'yapi-to-typescript'
    
    console.log(RequestBodyType.form, RequestBodyType.json)
  • 说明:

请求数据类型。

responseBodyType

  • 类型:'json' | 'text' | 'xml' | 'raw'

    实际请这样使用:

    import { ResponseBodyType } from 'yapi-to-typescript'
    
    console.log(ResponseBodyType.text, ResponseBodyType.json)
  • 说明:

返回数据类型。

dataKey

  • 类型:string
  • 说明:

数据所在键,来自配置 dataKey

paramNames

  • 类型:string[]
  • 说明:

路径参数的名称列表。一般不用关心,路径参数值已经被预处理进了 path

queryNames

  • 类型:string[]
  • 说明:

查询参数的名称列表。一般不用关心,查询参数键值已经被预处理进了 path

requestDataJsonSchema

  • 类型:JSONSchema4
  • 说明:

请求数据的 JSON Schema,由配置 jsonSchema 开启。

responseDataJsonSchema

  • 类型:JSONSchema4
  • 说明:

返回数据的 JSON Schema,由配置 jsonSchema 开启。

requestFunctionName 3.20.0+

  • 类型:string
  • 说明:

请求函数的名称。

data

  • 类型:object
  • 说明:

请求数据中的非文件数据。

hasFileData

  • 类型:boolean
  • 说明:

请求数据中是否包含文件数据。

fileData

  • 类型:object
  • 说明:

请求数据中的文件数据。

getFormData

  • 类型:() => FormData
  • 说明:

如果这是一个涉及文件上传的接口,可通过 getFormData() 获取到 FormData 直接上传,该 FormData 内已经添加了 datafileData

extraInfo 3.27.0+

  • 类型:Record<string, any>
  • 说明:

额外信息,由配置 setRequestFunctionExtraInfo 定义。

基于浏览器 fetch 的示例

下面是一个基于浏览器原生 fetch 的示例,通过 cross-fetch,你也可以让它运行在一些未实现 fetch 接口的老旧浏览器、Node.js、React Native 上。

import fetch from 'cross-fetch'
import { RequestBodyType, RequestFunctionParams } from 'yapi-to-typescript'

export interface RequestOptions {
  /**
   * 是否返回 Blob 结果,适用某些返回文件流的接口。
   */
  returnBlob?: boolean
}

export enum RequestErrorType {
  NetworkError = 'NetworkError',
  StatusError = 'StatusError',
  BusinessError = 'BusinessError',
}

export class RequestError extends Error {
  constructor(
    public type: RequestErrorType,
    public message: any,
    public httpStatusOrBusinessCode: number = 0,
  ) {
    super(message instanceof Error ? message.message : String(message))
  }
}

export default async function request<TResponseData>(
  payload: RequestFunctionParams,
  options?: RequestOptions,
): Promise<TResponseData> {
  try {
    // 基础 URL,可以从载荷中拉取或者写死
    const baseUrl = payload.prodUrl

    // 完整 URL
    const url = `${baseUrl}${payload.path}`

    // fetch 选项
    const fetchOptions: RequestInit = {
      method: payload.method,
      headers: {
        ...(payload.hasFileData
          ? {}
          : payload.requestBodyType === RequestBodyType.json
          ? { 'Content-Type': 'application/json; charset=UTF-8' }
          : payload.requestBodyType === RequestBodyType.form
          ? {
              'Content-Type':
                'application/x-www-form-urlencoded; charset=UTF-8',
            }
          : {}),
      },
      body: payload.hasFileData
        ? payload.getFormData()
        : payload.requestBodyType === RequestBodyType.json
        ? JSON.stringify(payload.data)
        : payload.requestBodyType === RequestBodyType.form
        ? Object.keys(payload.data)
            .filter(key => payload.data[key] != null)
            .map(
              key =>
                `${encodeURIComponent(key)}=${encodeURIComponent(
                  payload.data[key],
                )}`,
            )
            .join('&')
        : undefined,
    }

    // 发起请求
    const [fetchErr, fetchRes] = await fetch(url, fetchOptions).then<
      [
        // 如果遇到网络故障,fetch 将会 reject 一个 TypeError 对象
        TypeError,
        Response,
      ]
    >(
      res => [null, res] as any,
      err => [err, null] as any,
    )

    // 网络错误
    if (fetchErr) {
      throw new RequestError(RequestErrorType.NetworkError, fetchErr)
    }

    // 状态错误
    if (fetchRes.status < 200 || fetchRes.status >= 300) {
      throw new RequestError(
        RequestErrorType.StatusError,
        `${fetchRes.status}: ${fetchRes.statusText}`,
        fetchRes.status,
      )
    }

    // 请求结果处理
    const res = options?.returnBlob
      ? await fetchRes.blob()
      : (fetchRes.headers.get('Content-Type') || '').indexOf(
          'application/json',
        ) >= 0
      ? await fetchRes
          .json()
          // 解析 JSON 报错时给个空对象作为默认值
          .catch(() => ({}))
      : await fetchRes.text()

    // 业务错误
    // 假设 code 为 0 时表示请求成功,其他表示请求失败,同时 msg 表示错误信息
    if (
      res != null &&
      typeof res === 'object' &&
      res.code != null &&
      res.code !== 0
    ) {
      throw new RequestError(RequestErrorType.BusinessError, res.msg, res.code)
    }

    // 适配 dataKey,取出 data
    const data: TResponseData =
      res != null &&
      typeof res === 'object' &&
      payload.dataKey != null &&
      res[payload.dataKey] != null
        ? res[payload.dataKey]
        : res

    return data
  } catch (err: unknown) {
    // 重试函数
    const retry = () => request<TResponseData>(payload, options)
    if (err instanceof RequestError) {
      // 网络错误处理
      if (err.type === RequestErrorType.NetworkError) {
        // 此处可弹窗说明原因:err.message,最好也提供重试操作,下面以原生 confirm 为例,建议替换为项目中使用到的弹窗组件
        const isRetry = confirm(`网络错误:${err.message},是否重试?`)
        if (isRetry) {
          return retry()
        }
        throw err
      }

      // 状态错误处理
      else if (err.type === RequestErrorType.StatusError) {
        // 用户未登录处理
        if (err.httpStatusOrBusinessCode === 401) {
          // 推荐在此处发起登录逻辑
        }
      }

      // 业务错误处理
      else if (err.type === RequestErrorType.BusinessError) {
        // 推荐弹个轻提示说明错误原因:err.message
        throw err
      }
    } else {
      throw err
    }
  }
}

基于小程序 request 的示例

下面是一个基于小程序原生 request 方法的示例,支持微信小程序、QQ 小程序、支付宝小程序、百度小程序、字节跳动小程序、钉钉小程序、京东小程序。

// TODO