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

如何在Promise.allSettled中实现可控拒绝并批量重试请求?

批量请求重试中的未处理Promise拒绝问题

需求背景

需要批量调用API接口,接口偶尔返回500错误(可安全重试),希望通过Promise.allSettled()捕获此类失败请求,收集对应URL后递归重试,且无需在外部添加try-catch来处理未捕获的Promise拒绝。

当前实现代码

批量请求函数

export async function batchRequest(poolLimit, array, iteratorFn, exception) {
  const promises = []
  const racers = new Set()

  for (const item of array) {
    const pro = Promise.resolve().then(() => iteratorFn(item, array))
    promises.push(pro)
    racers.add(pro)
    const clean = () => racers.delete(pro)
    pro.then(clean).catch(clean)

    if (racers.size >= poolLimit) await Promise.race(racers)
  }


  const results = await Promise.allSettled(promises)

  // 收集iteratorFn抛出的错误
  const rejected = results
    .filter(({ status, reason }) => status === 'rejected' && reason.name === exception)
    .map(({ reason }) => reason.error)

  // 递归重试失败的URL
  if (rejected.length) {
    await batchRequest(poolLimit, rejected, iteratorFn, exception)
  }
}

迭代器函数

async function runRequest(url) {
  try {
    const { data } = await axios('https://exampleAPI.com')
    // 将数据写入某处...
  
  } catch (error) {
    if (error.response.status === 500) {
      throw { name: 'timeout', url }
    }
  }
}

const urls = [...大量URL]
await batchRequest(100, urls, runRequest, 'timeout')

遇到的错误

This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason  "#<Object>".] { code: 'ERR_UNHANDLED_REJECTION' }

临时处理方式

在调用batchRequest时添加外部try-catch可抑制错误,但不符合Promise.allSettled()吸收错误的初衷:

const urls = [...大量URL]
try {
  await batchRequest(100, urls, runRequest, 'timeout')
} catch (error) {
  console.log(error)
}

解决方案

问题根源

  1. 迭代器函数存在未捕获的异常:当runRequest遇到非500错误时,没有处理该异常,导致Promise进入rejected状态。
  2. Promise.race未处理拒绝:循环中await Promise.race(racers)时,若有Promise拒绝且未被捕获,会直接触发全局未处理拒绝。
  3. 失败URL收集逻辑错误:代码中尝试取reason.error,但实际抛出的对象只有name和url属性,导致重试逻辑失效。

修复步骤

1. 完善迭代器函数的错误处理

确保所有错误都被处理,仅对需要重试的500错误抛出指定格式的异常,其他错误转为resolved状态:

async function runRequest(url) {
  try {
    const { data } = await axios('https://exampleAPI.com')
    // 将数据写入某处...
    return { status: 'success', url, data }
  } catch (error) {
    // 仅对500错误抛出重试标记的异常
    if (error.response?.status === 500) {
      throw { name: 'timeout', url }
    }
    // 其他错误直接返回,避免Promise拒绝
    return { status: 'failed', url, error }
  }
}

2. 修正batchRequest中的Promise.race处理

给Promise.race添加catch,避免未处理的拒绝向上冒泡:

if (racers.size >= poolLimit) {
  // 捕获race中的拒绝,防止触发全局未处理错误
  await Promise.race(racers).catch(() => {})
}

3. 修复失败URL的收集逻辑

正确提取抛出对象中的url属性,确保重试列表正确:

// 收集需要重试的URL
const rejected = results
  .filter(({ status, reason }) => status === 'rejected' && reason?.name === exception)
  .map(({ reason }) => reason.url)

完整修复后的batchRequest函数

export async function batchRequest(poolLimit, array, iteratorFn, exception) {
  const promises = []
  const racers = new Set()

  for (const item of array) {
    const pro = Promise.resolve().then(() => iteratorFn(item, array))
    promises.push(pro)
    racers.add(pro)
    const clean = () => racers.delete(pro)
    pro.then(clean).catch(clean)

    if (racers.size >= poolLimit) {
      await Promise.race(racers).catch(() => {})
    }
  }

  const results = await Promise.allSettled(promises)

  // 筛选并收集需要重试的URL
  const retryUrls = results
    .filter(res => res.status === 'rejected' && res.reason?.name === exception)
    .map(res => res.reason.url)

  // 递归重试
  if (retryUrls.length > 0) {
    await batchRequest(poolLimit, retryUrls, iteratorFn, exception)
  }
}

内容的提问来源于stack exchange,提问作者Álvaro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 17:05:28