tryCall

package version >0.4.0

shadcn any version

author: cmtlyt

update time: 2026/04/07 11:16:55

提供安全的函数调用机制,支持错误捕获、默认值回退和回调处理。

特性

  • 安全调用: 自动捕获函数执行过程中的错误
  • 默认值回退: 提供错误处理函数返回默认值
  • 异步支持: 同时支持同步和异步函数
  • 回调机制: 支持最终回调处理执行结果
  • 上下文绑定: 正确处理函数的 this 上下文
  • 类型安全: 完整的 TypeScript 类型支持

安装

npm
npm i @cmtlyt/lingshu-toolkit
shadcn
npx shadcn@latest add https://cmtlyt.github.io/lingshu-toolkit/r/sharedTryCall.json

用法

import { tryCall, tryCallFunc } from '@cmtlyt/lingshu-toolkit/shared'
// or
import { tryCall, tryCallFunc } from '@cmtlyt/lingshu-toolkit/shared/try-call'

基础用法

简单调用

// 同步函数调用
const result = tryCall(() => 1)
console.log(result) // 1

// 异步函数调用
const asyncResult = await tryCall(async () => 1)
console.log(asyncResult) // 1

错误处理

// 捕获错误并返回默认值
const result = tryCall(
  () => {
    throw new Error('error')
  },
  () => 2,
)
console.log(result) // 2

// 异步错误处理
const asyncResult = await tryCall(
  async () => {
    throw new Error('error')
  },
  () => 3,
)
console.log(asyncResult) // 3

使用 tryCallFunc 包装函数

// 包装一个函数,可以多次调用
const fn = tryCallFunc(() => 1)
console.log(fn()) // 1
console.log(fn()) // 1

// 包装带参数的函数
const divide = tryCallFunc((a: number, b: number) => {
  if (a % b) {
    throw new Error('error')
  }
  return a / b
})

console.log(divide(1, 1)) // 1
console.log(divide(1, 0)) // Infinity

高级用法

回调处理

// 成功回调
tryCall(
  () => 1,
  null,
  (result) => {
    console.log(result) // 1
  },
)

// 错误回调
tryCall(
  () => {
    throw new Error('error')
  },
  () => 2,
  (result) => {
    console.log(result) // 2
  },
)

上下文绑定

const obj = {
  num: 1,
  foo: tryCallFunc(
    function (this: any) {
      if (this.num++ % 2) {
        throw new Error('error')
      }
      return this.num
    },
    () => 0,
  ),
}

console.log(obj.foo()) // 0 (第一次调用,this.num=1,抛出错误,返回0)
console.log(obj.foo()) // 3 (第二次调用,this.num=2,返回3)
console.log(obj.foo.call({ num: 10 })) // 11 (使用 call 改变上下文)

不处理错误的情况

// 当没有提供错误处理函数时,错误会被抛出
tryCall(() => {
  throw new Error('error')
}) // 抛出 Error

// 异步函数同样会抛出错误
await tryCall(async () => {
  throw new Error('error')
}) // 抛出 Error

错误传播

// onError 中抛出错误会覆盖原始错误
tryCall(
  () => {
    throw new Error('error1')
  },
  () => {
    throw new Error('error2')
  },
) // 抛出 Error: error2

// onFinal 中抛出错误会覆盖所有之前的错误
tryCall(
  () => {
    throw new Error('error1')
  },
  (err) => err,
  () => {
    throw new Error('error3')
  },
) // 抛出 Error: error3

执行顺序

const order: string[] = []

tryCall(
  () => {
    order.push('1: 执行函数')
    return 'result'
  },
  (err) => {
    order.push('2: onError')
    return err
  },
  (result) => {
    order.push(`3: onFinal - ${result}`)
  },
)

console.log(order)
// ['1: 执行函数', '3: onFinal - result']

API Reference

tryCall

尝试调用一个函数并处理可能的错误。

function tryCall<R, E = never>(
  cb: () => R,
  onError?: ((err: any) => E) | null,
  onFinal?: (result: TryCallResult<R, E>) => void,
): TryCallResult<R, E>

参数

参数类型必填默认值描述
cb() => Rundefined要调用的函数,可以是同步或异步
onError(err: any) => E | nullundefined错误处理函数,接收错误并返回默认值
onFinal(result: TryCallFinalArgs<R, E>) => voidundefined最终回调函数,接收执行结果或错误

返回值

函数的执行结果或错误处理函数返回的默认值。类型为 TryCallResult<R, E>

tryCallFunc

包装一个函数,返回一个拦截错误的新函数。

function tryCallFunc<A extends any[], R, E = never>(
  cb: (...args: A) => R,
  onError?: ((err: any) => E) | null,
  onFinal?: (result: TryCallResult<R, E>) => void,
): (...args: A) => TryCallResult<R, E>

参数

参数类型必填默认值描述
cb(...args: A) => Rundefined要包装的函数
onError(err: any) => E | nullundefined错误处理函数
onFinal(result: TryCallFinalArgs<R, E>) => voidundefined最终回调函数,接收执行结果或错误

返回值

返回一个新函数,该函数接收与原函数相同的参数,并返回 TryCallResult<R, E>

类型定义

type TryCallResult<R, E> = [R] extends [never]
  ? E
  : R extends Promise<infer P>
    ? Promise<TryCallResultValue<P, E>>
    : TryCallResultValue<R, E>

type TryCallResultValue<R, E> = Awaited<[E] extends [never] ? R : R | E>

type TryCallFinalArgs<R, E> = Awaited<[E] extends [never] ? R | Error : R | E>
  • TryCallResult<R, E>: 函数返回值类型,支持同步和异步
  • TryCallResultValue<R, E>: 解析后的结果值类型
  • TryCallFinalArgs<R, E>: onFinal 回调接收的参数类型

使用场景

API 请求错误处理

import { tryCall } from '@cmtlyt/lingshu-toolkit/shared'

// 安全调用可能失败的 API 请求
const fetchData = tryCall(
  async () => {
    const response = await fetch('/api/data')
    return response.json()
  },
  (error) => {
    console.error('API 请求失败:', error)
    return { error: true, message: error.message }
  },
)

配置解析

import { tryCall } from '@cmtlyt/lingshu-toolkit/shared'

// 解析配置文件,失败时使用默认配置
const config = tryCall(
  () => JSON.parse(localStorage.getItem('config') || '{}'),
  () => ({
    theme: 'light',
    language: 'zh-CN',
  }),
)

DOM 操作

import { tryCall } from '@cmtlyt/lingshu-toolkit/shared'

// 安全操作 DOM 元素
const element = tryCall(
  () => document.querySelector('#app'),
  () => {
    console.warn('未找到目标元素')
    return null
  },
)

数据转换

import { tryCall } from '@cmtlyt/lingshu-toolkit/shared'

// 转换数据,失败时返回空数组
const items = tryCall(
  () => rawData.map((item) => transform(item)),
  () => [],
)

最佳实践

1. 为错误提供有意义的默认值

// ✅ 好的做法:提供有意义的默认值
const user = tryCall(
  () => JSON.parse(userData),
  () => ({ name: 'Guest', role: 'visitor' }),
)

// ❌ 不好的做法:返回 undefined 或 null
const user = tryCall(
  () => JSON.parse(userData),
  () => undefined,
)

2. 使用 tryCallFunc 包装需要多次调用的函数

// ✅ 好的做法:包装函数,可以多次调用
const safeDivide = tryCallFunc((a: number, b: number) => {
  if (b === 0) throw new Error('Division by zero')
  return a / b
}, () => 0)

const result1 = safeDivide(10, 2) // 5
const result2 = safeDivide(10, 0) // 0

// ❌ 不好的做法:每次都创建新的包装函数
const result1 = tryCall(() => divide(10, 2), () => 0)
const result2 = tryCall(() => divide(10, 0), () => 0)

3. 利用 onFinal 进行统一处理

// ✅ 好的做法:使用 onFinal 统一处理结果
const result = tryCall(
  () => fetchData(),
  (error) => ({ error: true, data: null }),
  (finalResult) => {
    // 记录日志、更新 UI 状态等
    logResult(finalResult)
  },
)

4. 区分同步和异步错误处理

// ✅ 好的做法:正确处理异步错误
const asyncResult = await tryCall(
  async () => {
    const data = await fetchData()
    return processData(data)
  },
  (error) => {
    return { error: true, message: error.message }
  },
)

// ❌ 不好的做法:忘记 await
const asyncResult = tryCall(
  async () => {
    const data = await fetchData()
    return processData(data)
  },
  (error) => {
    return { error: true, message: error.message }
  },
)

5. 保持错误处理函数简单

// ✅ 好的做法:错误处理函数简单明了
const result = tryCall(
  () => complexOperation(),
  (error) => {
    return { success: false, error: error.message }
  },
)

// ❌ 不好的做法:错误处理函数过于复杂
const result = tryCall(
  () => complexOperation(),
  (error) => {
    if (error instanceof NetworkError) {
      return { success: false, code: 'NETWORK_ERROR' }
    } else if (error instanceof ValidationError) {
      return { success: false, code: 'VALIDATION_ERROR' }
    } else {
      return { success: false, code: 'UNKNOWN_ERROR' }
    }
  },
)

注意事项

⚠️ 错误处理

  • 如果不提供 onError 参数,函数执行过程中的错误会被抛出
  • 对于异步函数,如果不提供 onError,错误会以 rejected Promise 的形式抛出
  • 对于异步函数,提供 onError 后,错误会被捕获并转换为 resolved Promise
  • onError 中抛出的错误会覆盖原始错误
  • onFinal 中抛出的错误会覆盖所有之前的错误

⚠️ 类型检查

  • cb 参数必须是函数类型,否则会抛出 TypeError
  • 类型系统会在编译时检查函数参数和返回值的类型

⚠️ 执行顺序

  • 同步函数:执行函数 → onError(如果有错误)→ onFinal
  • 异步函数:执行函数 → 等待 Promise → onError(如果有错误)→ onFinal
  • onFinal 总是会被调用,无论成功或失败
  • onFinal 接收的参数优先级:errorResult > error > oriResult

🔧 执行机制

  • 函数调用时会自动处理 this 上下文绑定
  • 异步函数的错误会被自动捕获并转换为 Promise 结果
  • onFinal 回调会在函数执行完成后被调用,无论成功或失败

⚠️ 使用限制

  • 适用于需要安全调用可能抛出错误的函数的场景
  • 适用于需要为错误提供默认值的场景
  • 适用于需要在函数执行后进行统一处理的场景
  • 不适用于需要复杂错误处理逻辑的场景,建议使用 try-catch 块