如何实现强类型的Promise.allSettled替代方法并支持显式类型指定?
问题描述
我想实现一个类似Promise.allSettled的工具方法,行为接近Promise.all:接收一组Promise(可能带强类型,也可能没有),执行Promise.allSettled后返回结果数组——Promise成功则返回其值,失败则返回null。关键要保证结果数组的每个元素类型和输入数组中对应Promise的返回值强匹配(未强类型的Promise则对应unknown)。
目前我写的简化代码如下:
type Task<T> = (...args: any) => Promise<T> /** * Takes in a list of promises, and runs them in parallel * If the promise resolves, return the value * If the promise rejects, log the error and return null */ export async function runTasks<T>(tasks: Array<Task<T>>) { const results = await Promise.allSettled(tasks) return results.map((result, i) => { if (result.status === 'rejected') { logger.error(`Batch promise ${tasks[i].name} rejected with ${result.reason}`) return null } return result.value }) as T[] // 我知道这里类型断言不对 }
另外我希望调用者能显式指定Promise类型,比如:
const [hasPermission] = await runTasks([<Promise<boolean>>client.checkPermissions()])
现在所有返回值类型都是unknown,但我觉得(尤其是显式指定类型时)所需的类型信息是存在的,只是对泛型不太熟悉不知道怎么获取。请问这个需求能实现吗?
解决方案
当然可以实现,核心是利用元组泛型保留输入数组里每个元素的具体类型,而不是用统一的数组类型。另外你当前的Task类型定义的是返回Promise的函数,但你的调用例子里传的是Promise实例,所以先调整函数输入类型,兼容两种情况(如果只需要支持函数数组也可以单独调整)。
1. 完整实现代码
import logger from './your-logger-path'; // 提取Promise的resolve值类型 type PromiseResolveType<P> = P extends Promise<infer U> ? U : never; // 兼容Promise实例和返回Promise的函数 type TaskOrPromise<T> = T | (() => T); /** * 并行执行一组Promise或返回Promise的函数 * Promise成功则返回对应值,失败则记录错误并返回null * @param tasks Promise实例或返回Promise的函数组成的数组 */ export async function runTasks<T extends Array<TaskOrPromise<Promise<any>>>>( tasks: [...T] ): Promise<{ [K in keyof T]: PromiseResolveType<Awaited<T[K]>> | null }> { // 把函数转换成Promise实例(如果是函数的话执行它) const promises = tasks.map(task => typeof task === 'function' ? task() : task); const results = await Promise.allSettled(promises); return results.map((result, index) => { if (result.status === 'rejected') { const taskName = typeof tasks[index] === 'function' ? tasks[index].name : `task ${index}`; logger.error(`Batch promise ${taskName} rejected with ${result.reason}`); return null; } return result.value; }) as { [K in keyof T]: PromiseResolveType<Awaited<T[K]>> | null }; }
2. 调用示例
自动推断类型
输入不同类型的Promise,返回值类型会自动对应:
// 返回值类型:[string | null, number | null] const [userName, userId] = await runTasks([ Promise.resolve('Alice'), Promise.resolve(123) ]);
显式指定类型
如果遇到类型无法自动推断的场景,可以手动指定泛型:
// 显式指定输入元组类型,返回值类型:[boolean | null] const [hasPermission] = await runTasks<[Promise<boolean>]>([ client.checkPermissions() ]);
关键细节
[...T]元组语法:让TypeScript把输入数组推断为元组而不是普通数组,这样能保留每个元素的具体类型。Awaited<T[K]>:处理嵌套Promise的情况,比如函数返回Promise<Promise<string>>时,能正确提取到最内层的string类型。- 映射返回类型:
{ [K in keyof T]: ... }确保结果数组的每个位置的类型,和输入对应位置的Promise成功值类型+null严格匹配,完全符合你的强类型需求。
适配原有代码的调整
如果只需要支持返回Promise的函数数组(不需要直接传Promise实例),可以把TaskOrPromise改回你原来的定义:
type Task<T> = (...args: any) => Promise<T>; // 然后修改函数的泛型约束: export async function runTasks<T extends Array<Task<any>>>( tasks: [...T] ): Promise<{ [K in keyof T]: PromiseResolveType<Awaited<ReturnType<T[K]>>> | null }> { // 剩下的逻辑类似,不需要处理非函数的情况 const promises = tasks.map(task => task()); // ... }
这样调整后,你的调用方式和类型需求都能满足,不会再出现返回unknown的问题。
内容的提问来源于stack exchange,提问作者Robin Wilborn
相关产品推荐
相关产品推荐

