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
相关产品推荐
相关产品推荐

