Skip to content

to Promise 错误处理

介绍

to 是 Promise 错误处理工具,采用 [error, data] 元组模式简化异步操作的错误处理。该模式避免了繁琐的 try-catch 嵌套,让异步代码更加清晰易读。

核心特性:

  • 元组返回 - 返回 [error, data] 格式,统一错误处理方式
  • 类型安全 - 完整的 TypeScript 泛型支持
  • 条件执行 - 支持条件判断后执行 Promise
  • 自动重试 - 内置重试机制,处理临时性失败
  • 验证集成 - 集成数据验证功能

基本用法

to 函数

将 Promise 包装为 [error, data] 元组:

typescript
import { to } from '@/utils/to'

// 基本用法
const fetchUser = async (id: string) => {
  const [error, user] = await to(api.getUser(id))

  if (error) {
    console.error('获取用户失败:', error.message)
    return null
  }

  return user
}

// 替代 try-catch
// 传统写法
try {
  const user = await api.getUser(id)
  console.log(user)
} catch (error) {
  console.error(error)
}

// to 写法
const [error, user] = await to(api.getUser(id))
if (error) {
  console.error(error)
} else {
  console.log(user)
}

链式调用

多个异步操作的顺序执行:

typescript
import { to } from '@/utils/to'

const processOrder = async (orderId: string) => {
  // 获取订单
  const [orderError, order] = await to(api.getOrder(orderId))
  if (orderError) {
    return { success: false, message: '获取订单失败' }
  }

  // 获取用户
  const [userError, user] = await to(api.getUser(order.userId))
  if (userError) {
    return { success: false, message: '获取用户失败' }
  }

  // 处理支付
  const [payError, payResult] = await to(api.processPayment(order, user))
  if (payError) {
    return { success: false, message: '支付处理失败' }
  }

  return { success: true, data: payResult }
}

自定义错误处理

typescript
import { to } from '@/utils/to'

// 带默认值的错误处理
const getUserOrDefault = async (id: string) => {
  const [error, user] = await to(api.getUser(id))

  // 错误时返回默认用户
  if (error) {
    return {
      id: '0',
      name: '访客',
      role: 'guest'
    }
  }

  return user
}

// 错误转换
const fetchData = async () => {
  const [error, data] = await to(api.getData())

  if (error) {
    // 转换为业务错误
    throw new BusinessError('数据加载失败', error)
  }

  return data
}

条件执行

toIf 函数

根据条件决定是否执行 Promise:

typescript
import { toIf } from '@/utils/to'

// 条件为真时执行
const maybeRefresh = async (shouldRefresh: boolean) => {
  const [error, data] = await toIf(
    shouldRefresh,
    () => api.refreshData()
  )

  if (error) {
    console.error('刷新失败:', error)
    return null
  }

  // shouldRefresh 为 false 时,data 为 undefined
  return data
}

// 实际应用
const loadData = async (forceRefresh = false) => {
  // 检查缓存
  const cached = cache.get('data')

  // 有缓存且不强制刷新时,不调用 API
  const [error, freshData] = await toIf(
    !cached || forceRefresh,
    () => api.getData()
  )

  if (error) {
    return cached // 降级使用缓存
  }

  if (freshData) {
    cache.set('data', freshData)
    return freshData
  }

  return cached
}

带验证的执行

typescript
import { toIf } from '@/utils/to'

// 验证通过后执行
const submitForm = async (form: FormData) => {
  const isValid = validateForm(form)

  const [error, result] = await toIf(isValid, () => api.submit(form))

  if (!isValid) {
    uni.showToast({ title: '请填写完整', icon: 'none' })
    return
  }

  if (error) {
    uni.showToast({ title: '提交失败', icon: 'none' })
    return
  }

  uni.showToast({ title: '提交成功' })
}

自动重试

toWithRetry 函数

失败时自动重试:

typescript
import { toWithRetry } from '@/utils/to'

// 默认重试 3 次
const fetchWithRetry = async () => {
  const [error, data] = await toWithRetry(() => api.getData())

  if (error) {
    console.error('重试 3 次后仍然失败:', error)
    return null
  }

  return data
}

// 自定义重试次数
const [error, data] = await toWithRetry(
  () => api.getData(),
  5 // 重试 5 次
)

// 自定义重试间隔
const [error, data] = await toWithRetry(
  () => api.getData(),
  3,    // 重试次数
  1000  // 间隔 1 秒
)

重试场景示例

typescript
import { toWithRetry } from '@/utils/to'

// 网络请求重试
const uploadFile = async (file: File) => {
  const [error, result] = await toWithRetry(
    () => api.uploadFile(file),
    3,    // 重试 3 次
    2000  // 间隔 2 秒
  )

  if (error) {
    uni.showToast({
      title: '上传失败,请检查网络',
      icon: 'none'
    })
    return null
  }

  return result
}

// 支付状态查询(轮询重试)
const checkPaymentStatus = async (orderId: string) => {
  const [error, status] = await toWithRetry(
    async () => {
      const res = await api.getPaymentStatus(orderId)
      // 未完成时抛出错误触发重试
      if (res.status === 'pending') {
        throw new Error('支付未完成')
      }
      return res
    },
    10,   // 最多查询 10 次
    3000  // 每 3 秒查询一次
  )

  return status
}

数据验证

toValidate 函数

执行 Promise 并验证返回数据:

typescript
import { toValidate } from '@/utils/to'

// 带验证的数据获取
const getValidUser = async (id: string) => {
  const [error, user] = await toValidate(
    api.getUser(id),
    (data) => data && data.id && data.name // 验证函数
  )

  if (error) {
    // error 可能是请求错误或验证失败
    console.error('获取用户失败或数据无效')
    return null
  }

  return user // 保证数据符合验证条件
}

// 复杂验证
interface Order {
  id: string
  items: Array<{ id: string; quantity: number }>
  total: number
}

const getValidOrder = async (orderId: string) => {
  const [error, order] = await toValidate<Order>(
    api.getOrder(orderId),
    (data) => {
      if (!data?.id) return false
      if (!Array.isArray(data.items) || data.items.length === 0) return false
      if (typeof data.total !== 'number' || data.total <= 0) return false
      return true
    }
  )

  return error ? null : order
}

实际应用场景

表单提交

typescript
import { to } from '@/utils/to'

const handleSubmit = async () => {
  // 表单验证
  const valid = await formRef.value.validate()
  if (!valid) return

  loading.value = true

  const [error, result] = await to(api.submitForm(formData))

  loading.value = false

  if (error) {
    uni.showModal({
      title: '提交失败',
      content: error.message || '请稍后重试',
      showCancel: false
    })
    return
  }

  uni.showToast({ title: '提交成功' })
  uni.navigateBack()
}

登录流程

typescript
import { to, toWithRetry } from '@/utils/to'

const handleLogin = async () => {
  // 1. 获取验证码
  const [captchaError, captcha] = await to(api.getCaptcha())
  if (captchaError) {
    uni.showToast({ title: '获取验证码失败', icon: 'none' })
    return
  }

  // 2. 登录请求(带重试)
  const [loginError, loginResult] = await toWithRetry(
    () => api.login({
      username: form.username,
      password: form.password,
      captcha: form.captcha,
      uuid: captcha.uuid
    }),
    2 // 失败重试 2 次
  )

  if (loginError) {
    uni.showToast({ title: loginError.message || '登录失败', icon: 'none' })
    return
  }

  // 3. 获取用户信息
  const [userError, userInfo] = await to(api.getUserInfo())
  if (userError) {
    console.warn('获取用户信息失败,使用默认值')
  }

  // 4. 保存状态
  userStore.setToken(loginResult.token)
  userStore.setUserInfo(userInfo || {})

  uni.switchTab({ url: '/pages/index/index' })
}

并行请求

typescript
import { to } from '@/utils/to'

const loadPageData = async () => {
  // 并行发起多个请求
  const [
    [userError, user],
    [ordersError, orders],
    [messagesError, messages]
  ] = await Promise.all([
    to(api.getUserInfo()),
    to(api.getOrders()),
    to(api.getMessages())
  ])

  // 分别处理每个请求的结果
  if (!userError) {
    userData.value = user
  }

  if (!ordersError) {
    orderList.value = orders
  }

  if (!messagesError) {
    messageList.value = messages
  }

  // 全部失败时显示错误
  if (userError && ordersError && messagesError) {
    showError('数据加载失败')
  }
}

文件上传

typescript
import { to, toWithRetry } from '@/utils/to'

const uploadImages = async (files: File[]) => {
  const results = []

  for (const file of files) {
    // 单个文件上传,失败重试
    const [error, result] = await toWithRetry(
      () => api.uploadFile(file),
      3,
      1000
    )

    if (error) {
      results.push({
        file: file.name,
        success: false,
        error: error.message
      })
    } else {
      results.push({
        file: file.name,
        success: true,
        url: result.url
      })
    }
  }

  // 统计结果
  const successCount = results.filter(r => r.success).length
  const failCount = results.length - successCount

  uni.showToast({
    title: `上传完成: ${successCount}成功, ${failCount}失败`,
    icon: 'none'
  })

  return results
}

API

函数列表

函数说明参数
toPromise 错误处理<T>(promise: Promise<T>)
toIf条件执行 Promise<T>(condition: boolean, fn: () => Promise<T>)
toValidate带验证的 Promise<T>(promise: Promise<T>, validator: (data: T) => boolean)
toWithRetry带重试的 Promise<T>(fn: () => Promise<T>, retries?: number, delay?: number)

类型定义

typescript
/**
 * Promise 结果元组类型
 */
type ToResult<T> = [Error, null] | [null, T]

/**
 * 包装 Promise,返回 [error, data] 元组
 * @param promise 要执行的 Promise
 * @returns [error, data] 元组
 */
function to<T>(promise: Promise<T>): Promise<ToResult<T>>

/**
 * 条件执行 Promise
 * @param condition 执行条件
 * @param fn 返回 Promise 的函数
 * @returns [error, data] 元组,条件为 false 时返回 [null, undefined]
 */
function toIf<T>(
  condition: boolean,
  fn: () => Promise<T>
): Promise<ToResult<T | undefined>>

/**
 * 执行 Promise 并验证结果
 * @param promise 要执行的 Promise
 * @param validator 验证函数
 * @returns [error, data] 元组,验证失败时 error 为验证错误
 */
function toValidate<T>(
  promise: Promise<T>,
  validator: (data: T) => boolean
): Promise<ToResult<T>>

/**
 * 带重试的 Promise 执行
 * @param fn 返回 Promise 的函数
 * @param retries 重试次数,默认 3
 * @param delay 重试间隔(ms),默认 1000
 * @returns [error, data] 元组
 */
function toWithRetry<T>(
  fn: () => Promise<T>,
  retries?: number,
  delay?: number
): Promise<ToResult<T>>

最佳实践

1. 统一错误处理

typescript
// 封装通用请求函数
const request = async <T>(
  fn: () => Promise<T>,
  errorMessage = '操作失败'
): Promise<T | null> => {
  const [error, data] = await to(fn())

  if (error) {
    uni.showToast({
      title: error.message || errorMessage,
      icon: 'none'
    })
    return null
  }

  return data
}

// 使用
const user = await request(
  () => api.getUser(id),
  '获取用户失败'
)

2. 类型安全使用

typescript
interface User {
  id: string
  name: string
}

// 明确泛型类型
const [error, user] = await to<User>(api.getUser(id))

if (user) {
  // TypeScript 知道 user 是 User 类型
  console.log(user.name)
}

3. 错误分类处理

typescript
const handleError = (error: Error) => {
  if (error.name === 'NetworkError') {
    uni.showToast({ title: '网络连接失败', icon: 'none' })
  } else if (error.name === 'AuthError') {
    uni.redirectTo({ url: '/pages/login/index' })
  } else {
    uni.showToast({ title: error.message, icon: 'none' })
  }
}

const [error, data] = await to(api.getData())
if (error) {
  handleError(error)
}

常见问题

1. error 和 data 的类型推断?

TypeScript 会自动推断类型,但需要进行 null 检查:

typescript
const [error, data] = await to(api.getData())

// 错误:data 可能为 null
console.log(data.name) // ❌

// 正确:先检查
if (data) {
  console.log(data.name) // ✅
}

2. 如何处理非 Error 类型的错误?

typescript
const [error, data] = await to(
  promise.catch(err => {
    // 统一转换为 Error
    if (err instanceof Error) {
      throw err
    }
    throw new Error(String(err))
  })
)

3. toWithRetry 什么时候停止重试?

  • 请求成功时立即返回
  • 达到最大重试次数后返回最后一次的错误
  • 每次重试之间会等待指定的延迟时间