如何在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) }
解决方案
问题根源
- 迭代器函数存在未捕获的异常:当
runRequest遇到非500错误时,没有处理该异常,导致Promise进入rejected状态。 Promise.race未处理拒绝:循环中await Promise.race(racers)时,若有Promise拒绝且未被捕获,会直接触发全局未处理拒绝。- 失败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
相关产品推荐
相关产品推荐

