网络请求

使用 http 模块完成请求、上传、下载与统一的响应处理

http 是 Oiyo 数据获取模块提供的轻量请求库,内置了配置派生、生命周期钩子、自动重试与请求中断能力。

发起第一个请求

直接使用默认实例 http 即可发起请求,无需任何前置配置。默认情况下,请求成功后只会拿到精炼后的 data

// 默认只返回精炼后的响应体
const data = await http.request('https://api.example.com/pet/1')

如果需要状态码、响应头等完整信息,传入 raw: true

const response = await http.request('/pet/1', { raw: true })

console.log(response.statusCode)
console.log(response.header)
console.log(response.data)

创建实例

真实项目通常不会直接用默认实例发请求,而是先用 createHttp 建立带 baseURL、超时和公共请求头等配置的公共实例。

src/libs/api.ts
export const api = createHttp({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
})

之后在业务代码里使用这个实例,资源地址只写相对路径即可:

await api.request('/pet/1')

三大接口

request

请求方法通过 method 指定,请求体通过 body 传入,查询参数通过 query 传入,query 会被自动序列化。

await http.request('/pet', {
  method: 'POST',
  body: { name: 'kitty' },
  query: { source: 'app' },
})

upload

uploadbody 对应 formDataname 是文件对应的字段名(默认 'file'),onProgress 用于监听上传进度。

await http.upload('/upload', {
  filePath: tempFilePath,
  name: 'file',
  body: { category: 'avatar' }, // 对应 formData
  onProgress: ({ progress }) => {
    console.log(`上传进度 ${progress}%`)
  },
})

download

download 下载完成后可通过 tempFilePath 获取临时文件地址,onProgress 用于监听下载进度。

const { tempFilePath } = await http.download('/file/report.pdf', {
  onProgress: ({ progress }) => {
    console.log(`下载进度 ${progress}%`)
  },
})

console.log(tempFilePath)

核心功能

派生带鉴权的实例

需要在已有实例基础上追加配置(例如登录后附带 Authorization)时,用 create 派生一个新实例。派生实例会与上层配置合并,同名钩子按层级顺序串联执行。

const authApi = api.create({
  headers: { Authorization: 'Bearer xxx' },
})

// 同时带上 Content-Type 与 Authorization
await authApi.request('/pet/1')
派生适合按“公共实例 → 鉴权实例 → 特定业务实例”的层次组织请求配置,避免在每个请求里重复写相同的头部。

拦截请求与响应

通过生命周期钩子在请求前后统一处理逻辑,例如注入 token、打点、统一错误提示。钩子通常声明在实例上,作用于该实例的所有请求。

const api = createHttp({
  onRequest({ options, request }) {
    console.log('发起请求', request)
  },
  onRequestError({ error }) {
    console.error('请求失败', error)
  },
  onResponse({ response }) {
    console.log('收到响应', response.statusCode)
  },
  onResponseError({ response }) {
    console.error('响应错误', response.statusCode)
  },
})

多层配置(公共 + 派生 + 单次请求)的同名钩子会串联执行,按配置层级顺序依次执行。因此公共实例上的钩子不会被派生实例覆盖,而是与其一同触发。

拓展请求配置

可以通过拓展 HttpCommonOptions 接口实现自定义的 options 属性。

// 可以放在 <srcDir> 目录中的任意 d.ts/ts 文件中。推荐放在 <srcDir>/types 目录下
declare module "@skiyee/oiyo/runtime" {
  interface HttpCommonOptions {
    // 自定义属性
    requiresAuth?: boolean;
  }
}

export {};

声明后,你将会在与 http 实例相关的 options 上拿到该属性。

const client = createHttp({
  onRequest: (ctx) => {
    // ctx.options.requiresAuth?: boolean
  }
})

const auth = client.create({
  onRequest: (ctx) => {
    // ctx.options.requiresAuth?: boolean
  }
})

auth.request('/request', { requiresAuth: true })
auth.upload('/upload', { requiresAuth: true })
auth.download('/download', { requiresAuth: true })

失败自动重试

在网络错误或响应命中重试状态码时,http 会自动重试。默认策略考虑了幂等性:

  • 带请求体的方法(POST / PUT / DELETE / PATCH)默认不重试,其余方法重试一次
  • 主动取消不参与重试

需要更特殊的重试时,可显式配置 retry / retryDelay / retryStatusCodes

await http.request('/pet/1', {
  retry: 3,
  retryDelay: 1000,
  retryStatusCodes: [500, 502, 503],
})
每次重试都会重新走一遍完整流程(包含钩子)。

中断进行中的请求

通过 createHttpAborter() 创建中断信号,把 signal 传入请求,就能控制在响应前的任意时刻中断。

const aborter = createHttpAborter()

const promise = http.request('/pet/1', { signal: aborter.signal })

// 任意时刻中断
aborter.abort()

try {
  await promise
}
catch (error) {
  // 中断错误 error.name === 'AbortError'
}

中断会覆盖钩子阶段与请求进行中的阶段:信号触发后会立即 reject。

处理请求错误

非 2xx 响应(且未开启 ignoreResponseError)会抛出 HttpError,其中携带完整的请求上下文,便于统一处理。

try {
  await http.request('/pet/999')
}
catch (e) {
  const error = e as HttpError
  console.log(error.request) // 请求资源
  console.log(error.options) // 请求配置
  console.log(error.response) // 完整响应
  console.log(error.data) // 响应体
}

如果希望非 2xx 响应不抛错、由业务自行判断,可开启 ignoreResponseError: true

查看 createHttp API 参考:配置项信息