Skip to content

最佳实践与常见场景

本页展示业务项目中常见的组合方式。示例中的 getAccessToken()clearSession()redirectToLogin()showWarning() / showError() 都是应用层函数, 请替换为项目实际的状态管理、页面跳转与提示实现。

边界

luch-request 只负责 HTTP 请求、Task 和统一错误契约;Token 刷新、业务状态码、 登录跳转与 UI 提示属于应用层策略。本页示例不是必须的固定模板。

luchMeta 实现全局toast 提示

将服务端地址、超时、公共请求头和全局 interceptor 集中到一个模块,业务代码只导入 该实例。luchMeta 保存业务控制信息,不会发送给服务端。

ts
import {
  createLuchRequest,
  LuchRequestError
} from 'luch-request'

const http = createLuchRequest({
  baseURL: 'https://api.example.com/v1',
  timeout: 10_000,
  header: {
    clientid: 'web'
  },
  luchMeta: {
    hideToast: false,
    hideErrToast: false
  }
})

http.interceptors.request.use((config) => {
  const token = getAccessToken()

  if (!token) {
    return config
  }

  return {
    ...config,
    header: {
      ...config.header,
      Authorization: `Bearer ${token}`
    }
  }
})

http.interceptors.response.use(
  (response, context) => {
    // 仅request/upload 请求采用 { code, msg } 业务响应体。
    // download 没有统一的 data;upload 是否校验业务码由项目自行明确。
    if (context.operation !== LuchOperation.DOWNLOAD) {
      return response
    }
    const meta = response.config.luchMeta ?? {}
    const body = response.data as {
      code?: number
      msg?: string
    }

    // 这里的 200 是示例项目的业务成功码,不是 HTTP statusCode。
    if (body.code === 401) {
      clearSession()
      redirectToLogin()
    }

    if (!meta.hideToast && body.code != null && body.code !== 200) {
      showWarning(body.msg ?? '请求失败')
    }

    return response
  },
  (error) => {
    const meta = error.config.luchMeta ?? {}
    const responseData = error.response?.data as {
      msg?: string
      message?: string
    } | undefined
    // responseData?.message 是服务器特殊异常返回非标响应体message
    const errorMessage = responseData?.msg
      ?? responseData?.message
      ?? error.message

    if (
      !meta.hideErrToast &&
      error.code !== LuchRequestError.ERR_CANCELED
    ) {
      showError(errorMessage)
    }

    throw error
  }
)

export default http

response error interceptor 收到的是 v4 统一的 LuchRequestError,请求管线会补齐 error.config,可以直接读取 error.codeerror.configerror.response。业务侧的 catch 则仍建议使用 isLuchRequestError() 收窄 unknown,见错误处理

error.response 存在时,说明请求已取得服务端响应(常见于 HTTP 状态未通过 validateStatus);示例优先使用响应体中的 msgmessage,再回退到 v4 统一的 error.message。网络失败通常没有 error.response,会直接显示 error.message

TIP

注意:showWarningshowError 如果使用uni.showToast应该延迟(如延迟100ms),因为业务代码可能存在uni.showLoading,会导致uni.showToast不显示;

为什么业务错误码在成功分支处理

HTTP 请求成功(例如 statusCode 为 200)与业务成功(例如响应体 code 为 200)是 两件事。validateStatus 通过后,响应会进入 fulfilled handler;因此业务码应在上例的 第一个 handler 判断。若希望业务码失败也进入 catch,应在这里明确 throw 一个错误, 并让全项目统一采用这一约定。

避免 401 重复提示

示例在 401 时只清理会话并跳转登录,不额外调用 showWarning()。如果 redirectToLogin() 本身会提示登录失效,这能避免同一事件出现两次提示。项目若不在跳转 时提示,也可以在该分支单独提示一次。

单次请求关闭全局提示

为不需要全局提示的请求传入 luchMeta,不要把 UI 控制字段混进 headerparams 或 请求体:

ts
await http.get('/users', {
  luchMeta: {
    hideToast: true,
    hideErrToast: true
  }
})

luchMeta 与实例默认值按第一层浅合并。字段含义由应用自行定义,适合保持为扁平、稳定的 标签;完整合并规则见配置与合并

取消请求不弹错误

取消是预期控制流,统一错误提示中应排除 ERR_CANCELED。上面的实例已经包含此判断;调用 处只需按页面或业务范围持有取消源:

ts
import {
  createCancelSource,
  LuchRequestError
} from 'luch-request'

const source = createCancelSource()
const pending = http.get('/users', {
  signal: source.signal
})

source.cancel('页面已离开')

try {
  await pending
} catch (error) {
  if (error.code !== LuchRequestError.ERR_CANCELED) {
    throw error
  }
}

跨平台项目使用 createCancelSource(),不要假设所有 uni-app 平台都有浏览器的 AbortController。取消与 Task 的区别见取消与原生 Task

BigInt 大整数响应

JavaScript number 只能安全表示 Number.MAX_SAFE_INTEGER9007199254740991) 以内的整数。订单号、雪花 ID 等大整数若由服务端以 JSON 数字返回,原生 JSON 解析后可能 静默丢失精度。

服务端可以调整时,优先将这类标识符按字符串返回:

json
{
  "orderId": "9007199254740993"
}

标识符通常不参与数值运算,使用字符串在所有 uni-app 平台上最简单,也避免 BigInt 无法 直接 JSON.stringify() 的限制。

服务端暂时不能调整、且响应体以数字字面量返回时,单次请求应让原生层保留文本,再用支持 大整数的 parser 完全接管 transformResponse

ts
import JSONbig from 'json-bigint'

const JSONBigInt = JSONbig({
  storeAsString: true
})

const response = await http.get('/orders/9007199254740993', {
  dataType: 'text',
  transformResponse: [
    (data) => JSONBigInt.parse(data as string)
  ]
})

console.log(response.data.orderId) // '9007199254740993'

单次 transformResponse 会整体替换实例默认转换数组,不能省略 dataType: 'text';否则 平台可能已先将大整数解析为 number,之后无法恢复精度。若业务确实需要整数运算,可在 应用层对已保留的字符串显式调用 BigInt(value),并避免将含 bigint 的对象直接传给 JSON.stringify()

多个服务端使用独立实例

不同服务端、鉴权规则或错误提示策略使用独立实例,不要在单个实例中通过 interceptor 反复切换 baseURL

ts
const coreApi = createLuchRequest({
  baseURL: 'https://api.example.com',
  timeout: 10_000
})

const uploadApi = createLuchRequest({
  baseURL: 'https://upload.example.com',
  timeout: 30_000
})

实例的默认配置和 interceptor 互不共享。上传与下载的配置边界见 上传与下载

业务码失败进入 catch

当业务层希望把 code !== 200 与网络错误一样交给调用方 catch 时,可在 response fulfilled handler 中抛出异常。全局 interceptor 同时作用于 requestuploaddownloaddownload() 的响应不保证有 data,因此应使用 context.operation 明确 只校验采用该业务响应体的操作。

ts
import {
  LuchOperation,
  LuchRequestError,
  createLuchRequest,
  isLuchRequestError
} from 'luch-request'

interface BusinessResponse {
  code: number
  msg?: string
}

const http = createLuchRequest()

http.interceptors.response.use((response, context) => {
  // 仅request/upload 请求采用 { code, msg } 业务响应体。
  // download 没有统一的 data;upload 是否校验业务码由项目自行明确。
  if (context.operation !== LuchOperation.DOWNLOAD) {
    return response
  }

  const data = response.data as BusinessResponse

  if (data.code !== 200) {
    throw new Error(data.msg ?? '业务请求失败')
  }

  return response
})

try {
  await http.get('/users/1')
} catch (error: unknown) {
  if (!isLuchRequestError<unknown, { data?: BusinessResponse }>(error)) {
    throw error
  }

  console.log(error.code)
  console.log(error.config)
  console.log(error.task)
  console.log(error.response)
  console.log(error.response?.data)
}

response fulfilled handler 中抛出的普通 Error 会统一转换为 LuchRequestError.ERR_INTERCEPTOR。请求管线会补齐 configtaskresponse, 原始 Error 保留在 error.cause;所以局部 catch 不会因使用 throw new Error() 而 丢失响应上下文。

也可以显式抛出统一错误:

ts
throw new LuchRequestError(
  data.msg ?? '业务请求失败',
  LuchRequestError.ERR_INTERCEPTOR
)

两种写法最终都能在局部 catch 中取得 error.configerror.taskerror.response,且错误码都是 ERR_INTERCEPTOR。前者更短,推荐用于业务码拒绝;后者 适合确实需要主动指定 luch-request 错误码或错误元数据的场景。当前没有独立的 ERR_BUSINESS,因此仅为 code !== 200 使用后者不会带来额外的错误分类能力。

常见误区

场景推荐做法原因
custom 传递 UI 标记使用 luchMetav4 已将 v3 的 custom 更名为 luchMeta
message 判断错误判断 error.codemessage 面向人类且可能随平台变化,错误码是稳定契约。
取消时显示网络异常排除 ERR_CANCELED取消通常由页面离开或用户操作触发。
把业务 code 当 HTTP 状态在 fulfilled handler 判断业务码HTTP 成功并不表示业务成功。
将 Token 写入每个请求在 request interceptor 追加 Authorization能避免重复,并保证 Token 在派发前读取。

v4 正式版 · 面向传统 uni-app 的 TypeScript-first 请求库