all:同时执行多个异步操作
- 使用说明
-
功能描述: 类似于
Promise.all和Promise.allSettled,等待一个由多个Promise组成的对象或数组中的所有Promise都完成(或者其中一个失败)。执行的所有错误和抛出的错误都会收集在AggregateError中。 -
参数:promise对象/promise数组
-
返回值:所有
promise执行后的结果数组或对象
-
使用代码示例
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(...) })
-
源码解析
// 定义一个泛型异步函数
all。 export async function all< // 泛型约束T可以是一个Promise数组或一个Promise对象。 T extends Record<string, Promise> | Promise[](promises: T) { // 根据
promises是数组还是对象,将其转换成一个统一格式的数组entries。 const entries = isArray(promises) ? promises.map(p => [null, p] as [null, Promise]) : 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[] ? PromiseValues : 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解析后的类型。 ) }
-
方法流程说明:
-
将输入的
promises转换为一个统一格式的entries数组,无论它是一个Promise数组还是一个Promise对象。 -
对于每个
entry,创建一个新的Promise来处理成功和失败的情况,并使用Promise.all等待所有这些新Promise完成。 -
如果所有
Promise都成功解析,根据promises是数组还是对象,返回一个包含所有结果的数组或对象。 -
如果有一个或多个
Promise失败,则抛出一个AggregateError,其中包含所有失败的Promise的异常。
defer:在异步流程中添加清理或错误处理逻辑
- 使用说明
-
功能描述:用来执行一个异步函数,同时提供注册回调的机制,在异步函数执行完成后执行特定回调操作。
-
参数:异步函数。
-
返回值:异步函数成功执行时,返回其响应结果,否则重新抛出错误。
-
使用代码示例
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) })
-
源码解析
// 定义一个异步泛型函数
defer。 export const defer = async ( //func是一个接受注册函数register的异步函数。 func: ( register: ( //register允许func注册一个回调函数fn,该函数在func执行完成后调用。 // 可以通过options指定是否在回调函数中重新抛出错误。 fn: (error?: any) => any, options?: { rethrow?: boolean } ) => void ) => Promise ): Promise => { // 初始化一个用于存放回调函数及其选项的数组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 }
-
方法流程说明:
-
定义一个
callbacks数组来存储注册的回调函数及其选项。 -
实现
register函数,该函数允许func注册回调函数和选项。 -
调用外部提供的(但在代码片段中未定义)
tryit函数执行func,并传递register函数给func。 -
等待
func完成执行,获取可能的错误err和响应response。 -
依次执行
callbacks数组中的回调函数,处理可能的错误。 -
如果任何一个回调函数中出现需要重新抛出的错误,并且其
rethrow选项为true,则重新抛出该错误。 -
如果
func执行时产生了错误,重新抛出该错误。 -
如果
func成功执行,返回其响应结果。
guard:执行一个函数,并提供错误处理的能力
- 使用说明
-
功能描述:
guard函数可以用来为函数调用提供额外的错误处理逻辑,特别是当你希望根据错误类型选择性地处理错误时。 -
参数:目标函数、指定错误对象得函数(可选)。
-
返回值:抛出原始或返回undefined。
-
使用代码示例
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
-
源码解析
// 定义一个泛型函数
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 extends Promise ? Promise<Awaited<ReturnType> | undefined> : ReturnType | 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 => result instanceof Promisetry { // 尝试执行
func并获取结果。 const result = func() // 如果result是一个Promise,使用catch方法应用_guard函数。 // 否则,直接返回result。 return isPromise(result) ? result.catch(_guard) : result } catch (err) { // 如果在执行func时抛出错误,使用_guard函数处理该错误。 return _guard(err) } }
-
方法流程说明:
-
尝试执行
func函数并捕获任何抛出的错误。 -
如果
func执行成功并返回一个Promise,那么使用catch方法捕获该Promise可能抛出的错误,并应用_guard函数。 -
如果
func执行成功并没有返回Promise,那么直接返回结果。 -
如果
func抛出错误,应用_guard函数来决定是否重新抛出错误或返回undefined。 -
如果提供了
shouldGuard函数,它将用来判断是否应该 "guard"(捕获并返回undefined)错误。如果shouldGuard函数返回false,则抛出原始错误;如果返回true或未提供shouldGuard函数,则返回undefined。
map:对一个数组中的每个元素执行一个异步映射函数
- 使用说明
-
功能描述:它用于对一个数组中的每个元素执行一个异步映射函数,并返回一个包含所有映射结果的新数组。这个函数是
Array.prototype.map方法的异步版本。 -
参数:数组,异步函数。
-
返回值:映射后的新数组。
-
使用代码示例
import { map } from 'radash'
const userIds = [1, 2, 3, 4]
const users = await map(userIds, async (userId) => { return await api.users.find(userId) })
-
源码解析
// 定义一个异步函数
map。 export const map = async <T, K>( // 第一个参数array是一个具有只读属性的泛型数组。 array: readonly T[], // 第二个参数asyncMapFunc是一个异步映射函数,它接受一个数组元素和它的索引, // 返回一个Promise,该Promise解析为新类型K的值。 asyncMapFunc: (item: T, index: number) => Promise ): 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 }
-
方法流程说明:
-
检查
array是否存在。 -
如果不存在,返回一个空数组。
-
初始化一个空数组
result用于存储映射结果,以及一个索引计数器index。 -
遍历
array中的每个元素。对于每个元素,调用异步映射函数asyncMapFunc并等待Promise解析。 -
将异步映射函数解析后的结果添加到
result数组中。 -
在遍历完所有元素之后,返回包含所有映射结果的
result数组。
parallel:并行地处理数组中的元素,并对每个元素执行一个异步函数
- 使用说明
-
功能描述:这个函数会限制同时进行的异步操作的数量,以避免同时启动过多的异步任务。
-
参数:限制数量(number)、需要被异步处理的元素数组、转换函数(将数组中的每个元素转换为一个异步操作)。
-
返回值:返回一个数组,该数组包含了按原数组顺序排序的所有成功的结果。
-
使用代码示例
// 定义一个异步泛型函数
parallel。 export const parallel = async <T, K>( //limit是一个数字,指定了可以同时运行的异步任务的最大数量。 limit: number, //array是一个只读数组,包含将要被异步处理的元素。 array: readonly T[], //func是一个函数,将数组中的每个元素转换为一个异步操作(返回 Promise)。 func: (item: T) => Promise ): Promise<K[]> => { // 将数组array转换为包含元素和它们索引的对象的数组work。 const work = array.map((item, index) => ({ index, item })) // 定义一个处理函数processor,它将异步处理work数组中的元素。 const processor = async (res: (value: WorkItemResult[]) => void) => { const results: WorkItemResult[] = [] 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[][] // 将所有的结果扁平化并根据索引排序,然后使用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) } -
源码解析
// 定义一个异步泛型函数
parallel。 export const parallel = async <T, K>( //limit是一个数字,指定了可以同时运行的异步任务的最大数量。 limit: number, //array是一个只读数组,包含将要被异步处理的元素。 array: readonly T[], //func是一个函数,将数组中的每个元素转换为一个异步操作(返回 Promise)。 func: (item: T) => Promise ): Promise<K[]> => { // 将数组array转换为包含元素和它们索引的对象的数组work。 const work = array.map((item, index) => ({ index, item })) // 定义一个处理函数processor,它将异步处理work数组中的元素。 const processor = async (res: (value: WorkItemResult[]) => void) => { const results: WorkItemResult[] = [] 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[][] // 将所有的结果扁平化并根据索引排序,然后使用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) }
-
这段代码中使用了几个未定义的函数和类型,如
tryit、list、fork和sort,以及类型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:对数组中的每个元素执行一个异步归约函数
- 使用说明
-
功能描述:它是
Array.prototype.reduce方法的异步版本,用于对数组中的每个元素执行一个异步归约函数,并返回最终的归约值。 -
参数:被归约处理的元素数组、异步归约函数。
-
返回值:返回最终归约的值。
-
使用代码示例
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 } }, {})
-
源码解析
// 定义一个异步泛型函数
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, // 第三个参数initValue是可选的初始值。 initValue?: K ): Promise => { // 检查初始值是否提供了。 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 }
-
方法流程说明:
-
检查是否提供了初始值
initValue。 -
如果没有提供初始值且数组为空,则抛出错误,因为无法从空数组中归约出一个值。
-
确定迭代的数组。如果提供了初始值,则迭代整个数组;如果没有提供初始值,则从数组的第二个元素开始迭代。初始化累加值
value。 -
如果提供了初始值,则使用该初始值;如果没有提供初始值,则使用数组的第一个元素作为初始累加值。
-
遍历数组,对每个元素调用异步归约函数
asyncReducer,并等待其返回的Promise解析。 -
更新累加值
value为asyncReducer返回的新值。在遍历完所有元素之后,返回最终的累加值。
retry:反复尝试执行一个异步操作,直到达到设置上限
- 使用说明
-
功能描述:用于反复尝试执行一个异步操作,直到成功或达到重试次数上限。如果操作失败,可以选择在重试之间设置延迟或使用退避函数(backoff)来计算延迟时间。
-
参数:条件对象options(包含:重复次数、延迟、退避函数)、失败执行的异步操作函数。
-
返回值:可能发挥undefined。
-
使用代码示例
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)
-
源码解析
// 定义一个异步泛型函数
retry。 export const retry = async ( //options对象包含重试策略的选项。 options: { times?: number // 重试次数,默认为 3。 delay?: number | null // 固定延迟时间,如果提供,则在重试之间等待这么多毫秒。 backoff?: (count: number) => number // 退避函数,可以根据重试次数来计算延迟时间。 }, //func是要执行的异步函数,它可能会失败。 func: (exit: (err: any) => void) => Promise ): Promise => { // 从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 }
-
方法流程说明:
-
从
options中获取重试次数、延迟和退避函数。 -
遍历从 1 到重试次数的范围。
-
在每次迭代中,尝试执行
func并捕获可能的错误和结果。 -
如果
func成功执行(没有错误),返回结果。 -
如果有错误,并且是通过
exit函数显式退出的,重新抛出原始错误。 -
如果达到了重试次数上限并且仍然失败,抛出最后一次的错误。
-
如果指定了延迟或退避函数,根据相应的策略等待一段时间后再重试。
-
如果执行到函数的末尾,返回
undefined作为占位符,因为逻辑上不应该到达这里。
sleep:提供一个延时机制
- 使用说明
-
功能描述:提供一个延时机制,通常用于异步操作中的暂停。
-
参数:暂停时间(ms)。
-
返回值:返回一个新的Promise。
-
使用代码示例
import { sleep } from 'radash'
await sleep(2000) // => waits 2 seconds
-
源码解析
// 定义一个名为
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:捕获函数在执行过程中可能抛出的同步或异步错误
- 使用说明
-
功能描述:
tryit是一个高阶函数。用于捕获函数在执行过程中可能抛出的同步或异步错误,并返回一个元组,其中包含错误对象或函数的返回值。这个函数的目的是提供一种安全执行任意函数并处理错误的方式。 -
参数:需要被捕获的函数。
-
返回值:返回一个新函数,该函数接收与传入函数相同的参数。
-
使用代码示例
import { tryit } from 'radash'
const findUser = tryit(api.users.find)
const [err, user] = await findUser(userId)
-
源码解析
// 定义一个泛型高阶函数
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 ? Promise<[Error, undefined] | [undefined, Awaited]> : [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] } } }
-
方法流程说明:
-
tryit函数接受一个函数func作为参数。 -
tryit返回一个新函数,这个新函数接受与func相同的参数。 -
当调用这个新函数时,它尝试执行
func。如果func成功执行,且其返回值不是Promise,新函数返回一个元组[undefined, result]。 -
如果
func返回一个Promise,新函数返回一个Promise,该Promise解析为元组[undefined, value]或[err, undefined],具体取决于Promise是成功解析还是被拒绝。 -
如果在执行
func时捕获到同步错误,新函数返回一个元组[err, undefined]。如果func的返回类型是Promise,那么新函数的返回类型也是Promise,否则返回类型就是元组。
文章转载自:雾散声声慢