You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现强类型的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 16:13:20