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

JavaScript中Promise与循环:如何避免请求超限并并行执行

问题描述

我在处理涉及API请求与Promise的业务场景,目前用axios-rate-limit将请求频率限制为每分钟最多30次:

const requestTiny = axiosRateLimit(axios.create({'baseURL': '*****'}), {maxRequests: 30, perMilliseconds: 60500})

核心函数syncProducts最初仅处理单个商店(BN)的逻辑,代码能正常运行且无请求超限:

export const syncProducts = async (req, res) => {
    let productsBN = []
    let products = await getProductsERP('BN')
    for (let prod of products) {
        const product = await getProductERP(prod.store, prod.id)
        productsBN.push(product)
    }
    res.status(201).json(productsBN)
}

其中getProductsERP返回产品ID列表,getProductERP通过API获取对应产品详情。

现在需要从多个商店(比如新增ORL)获取数据,且API请求限制按商店划分,并行处理商店能提升效率。但直接复制代码会串行执行,第二个商店需等第一个完成。于是尝试用Promise实现并行:

export const syncProducts = async (req, res) => {
    let productsORL = [], productsBN = []
    Promise.all([
        getProductsERP('ORL').then(async products => {
            productsORL = await Promise.all(products.map(async prod => await getProductERP(prod.store, prod.id)))
        }),
        getProductsERP('BN').then(async products => {
            productsBN = await Promise.all(products.map(async prod => await getProductERP(prod.store, prod.id)))
        })
    ])
    .then(res.status(201).json(productsBN.concat(productsORL)))
    .catch(error => res.status(409).json(error))
}

现在的问题是:getProductsERP执行时限流正常,两个商店能并行处理;但getProductERP执行时会同时发起所有请求,触发Too Many Requests错误。

请问哪里处理有误?如何让getProductERP的Promise遵守API请求限制?


解决方案

问题根源

你用Promise.all(products.map(...))会一次性把所有getProductERP请求发出去,完全绕过了axios-rate-limit的限流机制——因为限流是针对单个axios实例的请求频率控制,而批量并行请求会瞬间把额度用完。

原来的串行for...of循环能正常限流,是因为每次await getProductERP都会等待上一个请求完成后再发下一个,天然符合“每分钟30次”的节奏。

修复方案

要并行处理商店,但每个商店内部的getProductERP请求要串行或分批限流执行,而不是一次性全部发起。可以用以下两种方式:

方式1:保留串行请求(每个商店内逐个请求),但商店间并行

修改每个商店的处理逻辑,把Promise.all换成串行循环,同时保持商店级别的Promise.all并行:

export const syncProducts = async (req, res) => {
    try {
        // 并行处理两个商店
        const [productsBN, productsORL] = await Promise.all([
            // BN商店:逐个请求产品详情
            (async () => {
                const productIds = await getProductsERP('BN');
                const result = [];
                for (const prod of productIds) {
                    const product = await getProductERP(prod.store, prod.id);
                    result.push(product);
                }
                return result;
            })(),
            // ORL商店:逐个请求产品详情
            (async () => {
                const productIds = await getProductsERP('ORL');
                const result = [];
                for (const prod of productIds) {
                    const product = await getProductERP(prod.store, prod.id);
                    result.push(product);
                }
                return result;
            })()
        ]);
        res.status(201).json([...productsBN, ...productsORL]);
    } catch (error) {
        res.status(409).json(error);
    }
}

这种方式和你最初的单个商店逻辑一致,只是把两个商店的处理包装成异步函数,用Promise.all并行执行,既保证了商店间的效率,又让每个商店内的请求遵守限流规则。

方式2:分批并行请求(兼顾效率与限流)

如果想在单个商店内也并行一部分请求(比如同时发5个,而不是逐个),可以自己实现分批处理逻辑,或者用p-limit这类库来控制并发数:

  1. 先安装p-limit:
npm install p-limit
  1. 修改代码:
import pLimit from 'p-limit';

// 限制单个商店内同时发起3个请求(根据限流额度调整)
const limit = pLimit(3);

export const syncProducts = async (req, res) => {
    try {
        const [productsBN, productsORL] = await Promise.all([
            (async () => {
                const productIds = await getProductsERP('BN');
                // 用p-limit包装请求,控制并发数
                return Promise.all(productIds.map(prod => limit(() => getProductERP(prod.store, prod.id))));
            })(),
            (async () => {
                const productIds = await getProductsERP('ORL');
                return Promise.all(productIds.map(prod => limit(() => getProductERP(prod.store, prod.id))));
            })()
        ]);
        res.status(201).json([...productsBN, ...productsORL]);
    } catch (error) {
        res.status(409).json(error);
    }
}

这种方式可以在不触发限流的前提下,提升单个商店内的请求效率,并发数可以根据你的限流额度(每分钟30次)调整,比如设为5或6,避免瞬间请求过多。

额外注意点

  • 确保getProductERP内部使用的是你配置了限流的requestTiny实例,而不是新创建的axios实例,否则限流规则不会生效。
  • 你的原代码中Promise.all的then方法里直接调用res.status(201).json(...)会导致响应提前发送,因为res.status(...)是同步执行的,正确的做法是在then的回调函数里处理响应,或者用async/await的写法(如上面的解决方案),避免回调地狱和异步问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 02:10:39