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

JavaScript中如何终止Promise.race中落败的Promise执行?

解决AbortController无法直接终止异步函数执行的问题

你的核心痛点是:当前通过手动检查doAbort标志的方式,只能在异步步骤间隙终止执行,没法在收到abort信号时立即中断正在进行的异步操作,而且在abort回调里调用reject或抛错无法直接影响_aget_Cards的执行流程。下面给出更高效的实现方案:

关键改进点

  • 利用AbortSignal.throwIfAborted():这个方法会在信号已中止时同步抛出AbortError,能直接中断当前的异步执行流程,无需手动检查标志。
  • 让异步函数返回Promise而非依赖回调:摒弃手动传递resolve/reject的写法,符合现代异步代码规范,也更便于结合AbortController使用。
  • 让底层异步操作支持AbortSignal:如果aget_CardDetails_List是基于网络请求或其他支持中止的API,直接传入signal参数,能真正终止底层操作,避免资源浪费。

重构后的代码示例

1. 改造外层函数xxx

async function xxx(userCN: string, timeout: number): Promise<CardDetailsWithCCBal[] | unknown> {
    const controller = new AbortController();
    
    // 封装超时逻辑
    const timeoutPromise = new Promise<never>((_, reject) => {
        setTimeout(() => {
            reject(new Error('CONTROLLER_TIMEOUT'));
            controller.abort(); // 触发中止信号
        }, timeout);
    });

    // 让_aget_Cards返回Promise,不再传递resolve/reject
    const cardPromise = _aget_Cards(userCN, controller.signal);

    try {
        return await Promise.race([cardPromise, timeoutPromise]);
    } catch (error) {
        if ((error as Error).message === 'CONTROLLER_TIMEOUT') {
            console.log('超时触发,已发送中止信号');
        }
        throw error; // 继续抛出错误让上层处理
    }
}

2. 改造_aget_Cards函数

async function _aget_Cards(userCN: string, signal: AbortSignal): Promise<CardDetailsWithCCBal[]> {
    // 先检查是否已中止,避免执行不必要的代码
    signal.throwIfAborted();

    let start_time = Date.now();
    // 在await前检查中止信号,一旦中止直接抛出错误
    const cardDetails_lists: CardDetails[][] = await Promise.all([
        aget_CardDetails_List(userCN, true, signal), // 传递signal给底层函数
        aget_CardDetails_List(userCN, false, signal)
    ]);
    console.log(`获取主次卡列表耗时 ${(Date.now() - start_time)/1000}s`);

    signal.throwIfAborted(); // 排序前再次检查
    start_time = Date.now();
    const cardDetails_list: CardDetails[] = [...cardDetails_lists[0], ...cardDetails_lists[1]];
    cardDetails_list.sort((a, b) => {
        const a_sortVal = `${a.cardType}${a.cardNo}`;
        const b_sortVal = `${b.cardType}${b.cardNo}`;
        return a_sortVal.localeCompare(b_sortVal);
    });

    signal.throwIfAborted(); // 后续步骤前都可以加检查
    // ... 剩余逻辑

    return cardDetails_list as CardDetailsWithCCBal[];
}

3. 改造底层函数aget_CardDetails_List(以fetch为例)

如果aget_CardDetails_List是基于fetch请求,直接传入signal参数就能真正中断网络请求:

async function aget_CardDetails_List(userCN: string, isPrimary: boolean, signal: AbortSignal): Promise<CardDetails[]> {
    // 检查信号,避免发起请求
    signal.throwIfAborted();
    
    const response = await fetch(`/api/cards?userCN=${userCN}&primary=${isPrimary}`, {
        signal // 传递signal给fetch,中止时会直接中断请求
    });
    
    if (!response.ok) {
        throw new Error('获取卡列表失败');
    }
    
    return response.json();
}

为什么之前的方案无效?

  • 你之前在abort事件回调里设置doAbort = true,但回调是异步触发的。当_aget_Cards卡在await Promise.all时,回调的执行不会打断await,只有等await完成后才会检查doAbort标志,没法立即终止。
  • 在abort回调里调用reject或抛错,因为回调不在_aget_Cards的执行栈中,无法直接中断当前的异步流程,只会让回调本身的执行抛出错误,不会影响_aget_Cards的主流程。

额外提示

  • 如果底层操作不支持AbortSignal(比如某些自定义异步逻辑),可以在循环或关键步骤中调用signal.throwIfAborted(),确保能及时响应中止信号。
  • 可以用signal.addEventListener('abort', ...)做一些清理工作,但终止流程还是靠throwIfAborted()更直接。

内容的提问来源于stack exchange,提问作者Youri Malleck-Ahmed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 12:44:56