lodash已死?Radash库方法介绍及源码解析 —— 异步方法篇

写在前面

tips:点赞 + 收藏 = 学会!

  • 我们前面已经介绍了 radash 的相关信息和所有 Array 相关方法,详情可前往主页查看。
  • 本篇我们继续介绍radash中异步相关的方法。
  • 所有方法分享完毕后,后续作者也会整理出 Radash 库所有方法的使用目录,包括文章说明脑图说明
    • 因为方法较多,后续将专门发布一篇总结文档,方便大家查阅使用。
    • 所有方法的思维导图说明后续也会上传至 github 和 gitee,有需要的可以访问下载。

all:同时执行多个异步操作

  1. 使用说明
    1. 功能描述: 类似于 Promise.allPromise.allSettled,等待一个由多个 Promise 组成的对象或数组中的所有 Promise 都完成(或者其中一个失败)。执行的所有错误和抛出的错误都会收集在 AggregateError 中。
    2. 参数:promise对象/promise数组
    3. 返回值:所有 promise 执行后的结果数组或对象
  2. 使用代码示例
    import { all } from 'radash'
    
    // 传入promise数组
    const [user] = await all([
      api.users.create(...),
      s3.buckets.create(...),
      slack.customerSuccessChannel.sendMessage(...)
    ])
    
    // 传入对象
    const { user } = await all({
      user: api.users.create(...),
      bucket: s3.buckets.create(...),
      message: slack.customerSuccessChannel.sendMessage(...)
    })
    
  3. 源码解析
    // 定义一个泛型异步函数 `all`。
    export async function all<
      // 泛型约束 `T` 可以是一个 `Promise` 数组或一个 `Promise` 对象。
      T extends Record<string, Promise<any>> | Promise<any>[]
    >(promises: T) {
      // 根据 `promises` 是数组还是对象,将其转换成一个统一格式的数组 `entries`。
      const entries = isArray(promises)
        ? promises.map(p => [null, p] as [null, Promise<any>])
        : Object.entries(promises)
    
      // 使用 `Promise.all` 等待所有 `Promise` 完成,并处理每个 `Promise` 的结果和异常。
      const results = await Promise.all(
        entries.map(([key, value]) =>
          value
            .then(result => ({ result, exc: null, key })) // 如果成功,记录结果。
            .catch(exc => ({ result: null, exc, key })) // 如果失败,记录异常。
        )
      )
    
      // 筛选出所有出现异常的结果。
      const exceptions = results.filter(r => r.exc)
      // 如果有异常,抛出一个 `AggregateError`,包含所有异常。
      if (exceptions.length > 0) {
        throw new AggregateError(exceptions.map(e => e.exc))
      }
    
      // 如果输入的 `promises` 是数组,返回一个包含所有结果的数组。
      if (isArray(promises)) {
        return results.map(r => r.result) as T extends Promise<any>[]
          ? PromiseValues<T>
          : unknown
      }
    
      // 如果输入的 `promises` 是对象,将结果组合成一个新对象并返回。
      return results.reduce(
        (acc, item) => ({
          ...acc,
          [item.key!]: item.result // 使用断言 `item.key!`,因为我们知道 `key` 不会是 `null`。
        }),
        {} as { [K in keyof T]: Awaited<T[K]> } // 返回类型是一个对象,其键类型为 `T` 的键,值类型为 `T` 中 `Promise` 解析后的类型。
      )
    }
    
    • 方法流程说明:
      1. 将输入的 promises 转换为一个统一格式的 entries 数组,无论它是一个 Promise 数组还是一个 Promise 对象。
      2. 对于每个 entry,创建一个新的 Promise 来处理成功和失败的情况,并使用 Promise.all 等待所有这些新 Promise 完成。
      3. 如果所有 Promise 都成功解析,根据 promises 是数组还是对象,返回一个包含所有结果的数组或对象。
      4. 如果有一个或多个 Promise 失败,则抛出一个 AggregateError,其中包含所有失败的 Promise 的异常。

defer:在异步流程中添加清理或错误处理逻辑

  1. 使用说明
    1. 功能描述:用来执行一个异步函数,同时提供注册回调的机制,在异步函数执行完成后执行特定回调操作。
    2. 参数:异步函数。
    3. 返回值:异步函数成功执行时,返回其响应结果,否则重新抛出错误。
  2. 使用代码示例
    import { defer } from 'radash'
    
    await defer(async (cleanup) => {
      const buildDir = await createBuildDir()
    
      cleanup(() => fs.unlink(buildDir))
    
      await build()
    })
    
    await defer(async (register) => {
      const org = await api.org.create()
      register(async () => api.org.delete(org.id), { rethrow: true })
    
      const user = await api.user.create()
      register(async () => api.users.delete(user.id), { rethrow: true })
    
      await executeTest(org, user)
    })
    
  3. 源码解析
    // 定义一个异步泛型函数 `defer`。
    export const defer = async <TResponse>(
      // `func` 是一个接受注册函数 `register` 的异步函数。
      func: (
        register: (
          // `register` 允许 `func` 注册一个回调函数 `fn`,该函数在 `func` 执行完成后调用。
          // 可以通过 `options` 指定是否在回调函数中重新抛出错误。
          fn: (error?: any) => any,
          options?: { rethrow?: boolean }
        ) => void
      ) => Promise<TResponse>
    ): Promise<TResponse> => {
      // 初始化一个用于存放回调函数及其选项的数组 `callbacks`。
      const callbacks: {
        fn: (error?: any) => any
        rethrow: boolean
      }[] = []
    
      // 实现注册函数 `register`,它将回调函数及其选项添加到 `callbacks` 数组。
      const register = (
        fn: (error?: any) => any,
        options?: { rethrow?: boolean }
      ) =>
        callbacks.push({
          fn,
          rethrow: options?.rethrow ?? false
        })
    
      // 调用 `tryit` 函数执行 `func`,并传入 `register`。
      // `tryit` 函数不在提供的代码片段中,但我们可以假设它是一个错误处理函数,返回一个包含错误和响应的元组。
      const [err, response] = await tryit(func)(register)
    
      // 遍历 `callbacks` 数组,依次执行每个回调函数。
      for (const { fn, rethrow } of callbacks) {
        // 使用 `tryit` 函数调用回调,以捕获并处理任何抛出的错误。
        const [rethrown] = await tryit(fn)(err)
        // 如果回调函数中有错误被重新抛出,并且 `rethrow` 选项为 `true`,则重新抛出该错误。
        if (rethrown && rethrow) throw rethrown
      }
    
      // 如果 `func` 执行时有错误产生,重新抛出该错误。
      if (err) throw err
      // 如果 `func` 执行成功,返回响应结果。
      return response
    }
    
    • 方法流程说明:
      1. 定义一个 callbacks 数组来存储注册的回调函数及其选项。
      2. 实现 register 函数,该函数允许 func 注册回调函数和选项。
      3. 调用外部提供的(但在代码片段中未定义)tryit 函数执行 func,并传递 register 函数给 func
      4. 等待 func 完成执行,获取可能的错误 err 和响应 response
      5. 依次执行 callbacks 数组中的回调函数,处理可能的错误。
      6. 如果任何一个回调函数中出现需要重新抛出的错误,并且其 rethrow 选项为 true,则重新抛出该错误。
      7. 如果 func 执行时产生了错误,重新抛出该错误。
      8. 如果 func 成功执行,返回其响应结果。

guard:执行一个函数,并提供错误处理的能力

  1. 使用说明
    1. 功能描述:guard 函数可以用来为函数调用提供额外的错误处理逻辑,特别是当你希望根据错误类型选择性地处理错误时。
    2. 参数:目标函数、指定错误对象得函数(可选)。
    3. 返回值:抛出原始或返回undefined。
  2. 使用代码示例
    import { guard } from 'radash'
    
    const users = (await guard(fetchUsers)) ?? []
    
    const isInvalidUserError = (err: any) => err.code === 'INVALID_ID'
    const user = (await guard(fetchUser, isInvalidUserError)) ?? DEFAULT_USER
    
  3. 源码解析
    // 定义一个泛型函数 `guard`。
    export const guard = <TFunction extends () => any>(
      // 参数 `func` 是一个无参数的函数,它可能返回任何类型的值,包括 `Promise`。
      func: TFunction,
      // 可选参数 `shouldGuard` 是一个函数,它接受一个错误对象 `err`,
      // 并返回一个布尔值,指示是否应该 "guard" 这个错误。
      shouldGuard?: (err: any) => boolean
    // 函数的返回类型依赖于 `func` 的返回类型。如果 `func` 返回一个 `Promise`,
    // 则 `guard` 返回一个 `Promise`,该 `Promise` 解析为 `func` 的返回值或 `undefined`。
    // 如果 `func` 不返回 `Promise`,则 `guard` 返回 `func` 的返回值或 `undefined`。
    ): ReturnType<TFunction> extends Promise<any>
      ? Promise<Awaited<ReturnType<TFunction>> | undefined>
      : ReturnType<TFunction> | undefined => {
      // 定义一个内部函数 `_guard`,它接受一个错误对象 `err`。
      const _guard = (err: any) => {
        // 如果提供了 `shouldGuard` 函数并且该函数返回 `false`,
        // 表示不应该 "guard" 这个错误,则重新抛出该错误。
        if (shouldGuard && !shouldGuard(err)) throw err
        // 否则,返回 `undefined`。
        return undefined as any
      }
    
      // 定义一个类型守卫函数 `isPromise`,它检查一个值是否为 `Promise`。
      const isPromise = (result: any): result is Promise<any> =>
        result instanceof Promise
    
      try {
        // 尝试执行 `func` 并获取结果。
        const result = func()
        // 如果 `result` 是一个 `Promise`,使用 `catch` 方法应用 `_guard` 函数。
        // 否则,直接返回 `result`。
        return isPromise(result) ? result.catch(_guard) : result
      } catch (err) {
        // 如果在执行 `func` 时抛出错误,使用 `_guard` 函数处理该错误。
        return _guard(err)
      }
    }
    
    • 方法流程说明:
      1. 尝试执行 func 函数并捕获任何抛出的错误。
      2. 如果 func 执行成功并返回一个 Promise,那么使用 catch 方法捕获该 Promise 可能抛出的错误,并应用 _guard 函数。
      3. 如果 func 执行成功并没有返回 Promise,那么直接返回结果。
      4. 如果 func 抛出错误,应用 _guard 函数来决定是否重新抛出错误或返回 undefined
      5. 如果提供了 shouldGuard 函数,它将用来判断是否应该 "guard"(捕获并返回 undefined)错误。如果 shouldGuard 函数返回 false,则抛出原始错误;如果返回 true 或未提供 shouldGuard 函数,则返回 undefined

map:对一个数组中的每个元素执行一个异步映射函数

  1. 使用说明
    1. 功能描述:它用于对一个数组中的每个元素执行一个异步映射函数,并返回一个包含所有映射结果的新数组。这个函数是 Array.prototype.map 方法的异步版本。
    2. 参数:数组,异步函数。
    3. 返回值:映射后的新数组。
  2. 使用代码示例
    import { map } from 'radash'
    
    const userIds = [1, 2, 3, 4]
    
    const users = await map(userIds, async (userId) => {
      return await api.users.find(userId)
    })
    
  3. 源码解析
    // 定义一个异步函数 `map`。
    export const map = async <T, K>(
      // 第一个参数 `array` 是一个具有只读属性的泛型数组。
      array: readonly T[],
      // 第二个参数 `asyncMapFunc` 是一个异步映射函数,它接受一个数组元素和它的索引,
      // 返回一个 `Promise`,该 `Promise` 解析为新类型 `K` 的值。
      asyncMapFunc: (item: T, index: number) => Promise<K>
    ): Promise<K[]> => {
      // 如果传入的数组 `array` 不存在,则返回一个空数组。
      if (!array) return []
      // 初始化一个空数组 `result`,用于存放映射后的新值。
      let result = []
      // 初始化一个索引计数器 `index`。
      let index = 0
      // 使用 `for...of` 循环遍历数组 `array` 的每个元素。
      for (const value of array) {
        // 对每个元素调用 `asyncMapFunc` 映射函数,并等待其 `Promise` 解析。
        const newValue = await asyncMapFunc(value, index++)
        // 将解析后的新值添加到 `result` 数组中。
        result.push(newValue)
      }
      // 循环完成后,返回包含所有新值的数组 `result`。
      return result
    }
    
    • 方法流程说明:
      1. 检查 array 是否存在。如果不存在,返回一个空数组。
      2. 初始化一个空数组 result 用于存储映射结果,以及一个索引计数器 index
      3. 遍历 array 中的每个元素。
      4. 对于每个元素,调用异步映射函数 asyncMapFunc 并等待 Promise 解析。
      5. 将异步映射函数解析后的结果添加到 result 数组中。
      6. 在遍历完所有元素之后,返回包含所有映射结果的 result 数组。

parallel:并行地处理数组中的元素,并对每个元素执行一个异步函数

  1. 使用说明
    1. 功能描述:这个函数会限制同时进行的异步操作的数量,以避免同时启动过多的异步任务。
    2. 参数:限制数量(number)、需要被异步处理的元素数组、转换函数(将数组中的每个元素转换为一个异步操作)。
    3. 返回值:返回一个数组,该数组包含了按原数组顺序排序的所有成功的结果。
  2. 使用代码示例
    // 定义一个异步泛型函数 `parallel`。
    export const parallel = async <T, K>(
      // `limit` 是一个数字,指定了可以同时运行的异步任务的最大数量。
      limit: number,
      // `array` 是一个只读数组,包含将要被异步处理的元素。
      array: readonly T[],
      // `func` 是一个函数,将数组中的每个元素转换为一个异步操作(返回 Promise)。
      func: (item: T) => Promise<K>
    ): Promise<K[]> => {
      // 将数组 `array` 转换为包含元素和它们索引的对象的数组 `work`。
      const work = array.map((item, index) => ({
        index,
        item
      }))
      // 定义一个处理函数 `processor`,它将异步处理 `work` 数组中的元素。
      const processor = async (res: (value: WorkItemResult<K>[]) => void) => {
        const results: WorkItemResult<K>[] = []
        while (true) {
          // 从 `work` 数组的末尾取出一个元素进行处理。
          const next = work.pop()
          // 如果没有更多元素,调用回调函数 `res` 并传入结果数组 `results`。
          if (!next) return res(results)
          // 使用 `tryit` 函数执行 `func` 并处理结果或错误。
          const [error, result] = await tryit(func)(next.item)
          // 将结果或错误添加到 `results` 数组中。
          results.push({
            error,
            result: result as K,
            index: next.index
          })
        }
      }
      // 创建一个 `queues` 数组,它包含了 `limit` 个新的 Promise,每个 Promise 都由 `processor` 函数处理。
      const queues = list(1, limit).map(() => new Promise(processor))
      // 使用 `Promise.all` 等待所有的 `queues` 中的 Promise 完成。
      const itemResults = (await Promise.all(queues)) as WorkItemResult<K>[][]
      // 将所有的结果扁平化并根据索引排序,然后使用 `fork` 函数将结果分为错误和成功的结果。
      const [errors, results] = fork(
        sort(itemResults.flat(), r => r.index),
        x => !!x.error
      )
      // 如果有任何错误,抛出一个 `AggregateError` 包含所有错误。
      if (errors.length > 0) {
        throw new AggregateError(errors.map(error => error.error))
      }
      // 返回一个数组,它包含了按原数组顺序排序的所有成功的结果。
      return results.map(r => r.result)
    }
    
  3. 源码解析
    // 定义一个异步泛型函数 `parallel`。
    export const parallel = async <T, K>(
      // `limit` 是一个数字,指定了可以同时运行的异步任务的最大数量。
      limit: number,
      // `array` 是一个只读数组,包含将要被异步处理的元素。
      array: readonly T[],
      // `func` 是一个函数,将数组中的每个元素转换为一个异步操作(返回 Promise)。
      func: (item: T) => Promise<K>
    ): Promise<K[]> => {
      // 将数组 `array` 转换为包含元素和它们索引的对象的数组 `work`。
      const work = array.map((item, index) => ({
        index,
        item
      }))
      // 定义一个处理函数 `processor`,它将异步处理 `work` 数组中的元素。
      const processor = async (res: (value: WorkItemResult<K>[]) => void) => {
        const results: WorkItemResult<K>[] = []
        while (true) {
          // 从 `work` 数组的末尾取出一个元素进行处理。
          const next = work.pop()
          // 如果没有更多元素,调用回调函数 `res` 并传入结果数组 `results`。
          if (!next) return res(results)
          // 使用 `tryit` 函数执行 `func` 并处理结果或错误。
          const [error, result] = await tryit(func)(next.item)
          // 将结果或错误添加到 `results` 数组中。
          results.push({
            error,
            result: result as K,
            index: next.index
          })
        }
      }
      // 创建一个 `queues` 数组,它包含了 `limit` 个新的 Promise,每个 Promise 都由 `processor` 函数处理。
      const queues = list(1, limit).map(() => new Promise(processor))
      // 使用 `Promise.all` 等待所有的 `queues` 中的 Promise 完成。
      const itemResults = (await Promise.all(queues)) as WorkItemResult<K>[][]
      // 将所有的结果扁平化并根据索引排序,然后使用 `fork` 函数将结果分为错误和成功的结果。
      const [errors, results] = fork(
        sort(itemResults.flat(), r => r.index),
        x => !!x.error
      )
      // 如果有任何错误,抛出一个 `AggregateError` 包含所有错误。
      if (errors.length > 0) {
        throw new AggregateError(errors.map(error => error.error))
      }
      // 返回一个数组,它包含了按原数组顺序排序的所有成功的结果。
      return results.map(r => r.result)
    }
    
    • 这段代码中使用了几个未定义的函数和类型,如 tryitlistforksort,以及类型 WorkItemResult<K>。我们可以假设这些函数和类型具有以下功能:
      • tryit(func)(item):执行 func(item) 并捕获任何抛出的错误,返回一个包含错误和结果的元组。
      • list(1, limit):创建一个包含从 1 到 limit 的数字的数组。
      • fork(array, condition):分割数组 array,根据 condition 函数返回的布尔值将数组分为包含错误的元素和成功的元素两个数组。
      • sort(array, keySelector):根据 keySelector 函数返回的键对数组 array 进行排序。
      • WorkItemResult<K>:一个类型,表示工作项的结果,包含可能的 error、成功的 result 以及元素的 index

reduce:对数组中的每个元素执行一个异步归约函数

  1. 使用说明
    1. 功能描述:它是 Array.prototype.reduce 方法的异步版本,用于对数组中的每个元素执行一个异步归约函数,并返回最终的归约值。
    2. 参数:被归约处理的元素数组、异步归约函数。
    3. 返回值:返回最终归约的值。
  2. 使用代码示例
    import { reduce } from 'radash'
    
    const userIds = [1, 2, 3, 4]
    
    const users = await reduce(userIds, async (acc, userId) => {
      const user = await api.users.find(userId)
      return {
        ...acc,
        [userId]: user
      }
    }, {})
    
  3. 源码解析
    // 定义一个异步泛型函数 `reduce`。
    export const reduce = async <T, K>(
      // 第一个参数 `array` 是一个只读数组,包含将要被归约处理的元素。
      array: readonly T[],
      // 第二个参数 `asyncReducer` 是一个异步归约函数,它接受累加值 `acc`、当前元素 `item` 和它的索引 `index`,
      // 并返回一个 `Promise`,该 `Promise` 解析为新的累加值。
      asyncReducer: (acc: K, item: T, index: number) => Promise<K>,
      // 第三个参数 `initValue` 是可选的初始值。
      initValue?: K
    ): Promise<K> => {
      // 检查初始值是否提供了。
      const initProvided = initValue !== undefined
      // 如果没有提供初始值且数组为空,则抛出错误。
      if (!initProvided && array?.length < 1) {
        throw new Error('Cannot reduce empty array with no init value')
      }
      // 如果提供了初始值,使用整个数组;否则,从数组的第二个元素开始迭代。
      const iter = initProvided ? array : array.slice(1)
      // 初始化累加值 `value`。如果提供了初始值,使用它;否则使用数组的第一个元素。
      let value: any = initProvided ? initValue : array[0]
      // 使用 `for...of` 循环和 `entries` 方法遍历数组或其子数组。
      for (const [i, item] of iter.entries()) {
        // 对每个元素调用异步归约函数 `asyncReducer` 并等待其 `Promise` 解析。
        value = await asyncReducer(value, item, i)
      }
      // 循环完成后,返回最终的累加值 `value`。
      return value
    }
    
    • 方法流程说明:
      1. 检查是否提供了初始值 initValue
      2. 如果没有提供初始值且数组为空,则抛出错误,因为无法从空数组中归约出一个值。
      3. 确定迭代的数组。如果提供了初始值,则迭代整个数组;如果没有提供初始值,则从数组的第二个元素开始迭代。
      4. 初始化累加值 value。如果提供了初始值,则使用该初始值;如果没有提供初始值,则使用数组的第一个元素作为初始累加值。
      5. 遍历数组,对每个元素调用异步归约函数 asyncReducer,并等待其返回的 Promise 解析。
      6. 更新累加值 valueasyncReducer 返回的新值。
      7. 在遍历完所有元素之后,返回最终的累加值。

retry:反复尝试执行一个异步操作,直到达到设置上限

  1. 使用说明
    1. 功能描述:用于反复尝试执行一个异步操作,直到成功或达到重试次数上限。如果操作失败,可以选择在重试之间设置延迟或使用退避函数(backoff)来计算延迟时间。
    2. 参数:条件对象options(包含:重复次数、延迟、退避函数)、失败执行的异步操作函数。
    3. 返回值:可能发挥undefined。
  2. 使用代码示例
    import { retry } from 'radash'
    
    await retry({}, api.users.list)
    await retry({ times: 10 }, api.users.list)
    await retry({ times: 2, delay: 1000 }, api.users.list)
    
    // exponential backoff
    await retry({ backoff: i => 10**i }, api.users.list)
    
  3. 源码解析
    // 定义一个异步泛型函数 `retry`。
    export const retry = async <TResponse>(
      // `options` 对象包含重试策略的选项。
      options: {
        times?: number // 重试次数,默认为 3。
        delay?: number | null // 固定延迟时间,如果提供,则在重试之间等待这么多毫秒。
        backoff?: (count: number) => number // 退避函数,可以根据重试次数来计算延迟时间。
      },
      // `func` 是要执行的异步函数,它可能会失败。
      func: (exit: (err: any) => void) => Promise<TResponse>
    ): Promise<TResponse> => {
      // 从 `options` 中获取重试次数、固定延迟时间和退避函数。
      const times = options?.times ?? 3
      const delay = options?.delay
      const backoff = options?.backoff ?? null
    
      // 使用 `range` 函数生成一个序列,并遍历这个序列进行重试。
      for (const i of range(1, times)) {
        // 尝试执行 `func` 函数,并捕获可能的错误 `err` 和结果 `result`。
        const [err, result] = (await tryit(func)((err: any) => {
          // 如果 `func` 失败,并使用 `exit` 函数退出,则抛出一个特殊的错误对象。
          throw { _exited: err }
        })) as [any, TResponse]
    
        // 如果没有错误,说明 `func` 成功执行,返回结果。
        if (!err) return result
    
        // 如果有特殊的退出错误,重新抛出原始错误。
        if (err._exited) throw err._exited
    
        // 如果是最后一次重试且仍然失败,抛出错误。
        if (i === times) throw err
    
        // 如果设置了固定延迟时间,使用 `sleep` 函数等待。
        if (delay) await sleep(delay)
    
        // 如果提供了退避函数,根据重试次数计算延迟时间并等待。
        if (backoff) await sleep(backoff(i))
      }
    
      // 如果代码执行到这里,说明逻辑上不应该到达的代码路径。
      // 这是为了满足 TypeScript 的严格模式要求。
      /* istanbul ignore next */
      return undefined as unknown as TResponse
    }
    
    • 方法流程说明:
      1. options 中获取重试次数、延迟和退避函数。
      2. 遍历从 1 到重试次数的范围。
      3. 在每次迭代中,尝试执行 func 并捕获可能的错误和结果。
      4. 如果 func 成功执行(没有错误),返回结果。
      5. 如果有错误,并且是通过 exit 函数显式退出的,重新抛出原始错误。
      6. 如果达到了重试次数上限并且仍然失败,抛出最后一次的错误。
      7. 如果指定了延迟或退避函数,根据相应的策略等待一段时间后再重试。
      8. 如果执行到函数的末尾,返回 undefined 作为占位符,因为逻辑上不应该到达这里。

sleep:提供一个延时机制

  1. 使用说明
    1. 功能描述:提供一个延时机制,通常用于异步操作中的暂停。
    2. 参数:暂停时间(ms)。
    3. 返回值:返回一个新的Promise。
  2. 使用代码示例
    import { sleep } from 'radash'
    
    await sleep(2000) // => waits 2 seconds
    
  3. 源码解析
    // 定义一个名为 `sleep` 的函数。
    export const sleep = (milliseconds: number) => {
      // 返回一个新的 Promise。
      return new Promise(res => 
        // 使用 `setTimeout` 函数设置一个定时器,它在 `milliseconds` 指定的毫秒数后执行。
        setTimeout(
          // 当定时器到时,调用 `res` 函数来解析这个 Promise。
          res,
          // 传递给 `setTimeout` 的毫秒数,它决定了延时的长度。
          milliseconds
        )
      )
    }
    
    • 方法流程说明:当你调用 sleep 函数并传入一个毫秒数时,它会返回一个 Promise。这个 Promise 不会立即解析,而是会等待你指定的时间长度。当时间到了之后,Promise 会被解析,然后你可以在 .then() 方法中继续执行后续的代码,或者你可以在 async 函数中使用 await 关键字来等待 Promise 解析。

tryit:捕获函数在执行过程中可能抛出的同步或异步错误

  1. 使用说明
    1. 功能描述:tryit 是一个高阶函数。用于捕获函数在执行过程中可能抛出的同步或异步错误,并返回一个元组,其中包含错误对象或函数的返回值。这个函数的目的是提供一种安全执行任意函数并处理错误的方式。
    2. 参数:需要被捕获的函数。
    3. 返回值:返回一个新函数,该函数接收与传入函数相同的参数。
  2. 使用代码示例
    import { tryit } from 'radash'
    
    const findUser = tryit(api.users.find)
    
    const [err, user] = await findUser(userId)
    
  3. 源码解析
    // 定义一个泛型高阶函数 `tryit`。
    export const tryit = <Args extends any[], Return>(
      // `func` 是一个接受任意参数的函数,其返回值可以是任何类型,包括 `Promise`。
      func: (...args: Args) => Return
    ) => {
      // 返回一个新函数,这个新函数接受与 `func` 相同的参数。
      return (
        ...args: Args
      // 新函数的返回类型取决于 `func` 的返回类型是否是 `Promise`。
      // 如果 `func` 返回 `Promise`,则返回一个 `Promise`,包含一个错误或函数返回值的元组。
      // 如果 `func` 返回非 `Promise`,则直接返回错误或函数返回值的元组。
      ): Return extends Promise<any>
        ? Promise<[Error, undefined] | [undefined, Awaited<Return>]>
        : [Error, undefined] | [undefined, Return] => {
        try {
          // 尝试执行 `func` 并获取结果。
          const result = func(...args)
          // 使用辅助函数 `isPromise` 检查 `result` 是否是 `Promise`。
          if (isPromise(result)) {
            // 如果是 `Promise`,使用 `then` 和 `catch` 方法处理结果或捕获错误。
            return result
              .then(value => [undefined, value]) // 成功时返回值的元组。
              .catch(err => [err, undefined]) // 错误时返回错误的元组。
          }
          // 如果 `result` 不是 `Promise`,直接返回值的元组。
          return [undefined, result]
        } catch (err) {
          // 如果执行 `func` 时捕获到同步错误,返回错误的元组。
          return [err as any, undefined]
        }
      }
    }
    
    • 方法流程说明:
      1. tryit 函数接受一个函数 func 作为参数。
      2. tryit 返回一个新函数,这个新函数接受与 func 相同的参数。
      3. 当调用这个新函数时,它尝试执行 func
      4. 如果 func 成功执行,且其返回值不是 Promise,新函数返回一个元组 [undefined, result]
      5. 如果 func 返回一个 Promise,新函数返回一个 Promise,该 Promise 解析为元组 [undefined, value][err, undefined],具体取决于 Promise 是成功解析还是被拒绝。
      6. 如果在执行 func 时捕获到同步错误,新函数返回一个元组 [err, undefined]
      7. 如果 func 的返回类型是 Promise,那么新函数的返回类型也是 Promise,否则返回类型就是元组。

写在后面

  • 后续我们会继续分享 Radash 库中其他方法的使用和源码解析。
  • 大家有任何问题或见解,欢迎评论区留言交流和批评指正!!!
  • 你的每一个点赞和收藏都是作者写作的动力!!!
  • 点击访问:radash官网
posted @ 2024-05-14 09:29  山里看瓜  阅读(326)  评论(0编辑  收藏  举报