【问题标题】:How to use Promise.all to handle thousands of request如何使用 Promise.all 处理数千个请求
【发布时间】:2020-12-16 12:14:29
【问题描述】:

在我的 React 应用程序中,我有一个向在线服务发送请求的组件,该服务最多可以处理 50 个请求。我现在收到一个执行 7000 个 MAC 的新请求。

function App() {
const [data, setData] = useState([]);

useEffect(() => {
     const fetchData = async () => {
        await axios.all([
             axios.get("/ipdn/<MAC ADDRESS>", { timeout: 10000 }),
             axios.get("/ipdn/<MAC ADDRESS>", { timeout: 10000 })
       // Adding all the mac address ....... 
        ]).then((responseArr) => {
            setData(responseArr)
        });
     };
    fetchData();
}, []);

我想扩展 fetchData 函数,所以基本上它只会发送 50 个 IP 并等待迭代完成。

当迭代完成后,接下来的 50 个将被执行。

谢谢

【问题讨论】:

  • 使用es6-promise-pool。它可以帮助您管理并行请求的并发性
  • 您能否完成您的代码,以便清楚地了解 MAC 地址的存储位置以及如何将它们传递给请求?
  • 呃,只是不要写一个客户端组件来执行7000个HTTP请求。

标签: javascript reactjs promise axios


【解决方案1】:

没有库,你可以使用这个函数:

function poolPromises(iterPromises, poolSize) {
    return new Promise((resolve, reject) => {
        let promises = [];
        function nextPromise() {
            let { value, done } = iterPromises.next();
            if (done) {
                resolve(Promise.all(promises));
            } else {
                promises.push(value); // value is a promise
                value.then(nextPromise, reject);
            }
            return !done;
        }
        
        while (promises.length < poolSize && nextPromise()) { }
    });
}

这个函数将接受来自迭代器的承诺,直到池大小。每当一个 Promise 解决时,它将从迭代器获得下一个 Promise,因此池再次完成。因此,在生成下一个 Promise 块之前,不必完全清空池。只要有空位,就会再次使用。

重要的是,迭代器仅在通过 next() 方法从其中拉出时创建下一个承诺。

在你的用例中,你可以这样称呼它:

const fetchData = async () => {
    function * iterRequests() {
        for (let macAddress of macAddresses) {
            yield axios.get("/ipdn/" + macAddress, { timeout: 10000 });
        }
    }
    return poolPromises(iterRequests(), 50).then(setData);
}    

注意:fetchData 不必声明为async,因为其中没有await

【讨论】:

  • while (promises.length &lt; poolSize &amp;&amp; nextPromise()) { } 和例如poolSize 一次最多50 个承诺,但通过*iterRequests 的请求超过50 个,如何处理所有承诺?有人通过let { value, done } = iterPromises.next(); 到达done 吗?
  • 第一批调用从 while 循环开始,通常在到达 poolSize 时结束,但是当这 50 个承诺中的一个解决时,调用相同的函数 nextPromise再次(通过then),一直持续到iterPromises 耗尽。所以最后所有的 Promise 都被创建并收集在 promises 数组中,在你的情况下,它的长度将达到 7000。
  • 我没有读过value.,因此我完全错过了它在value.then(nextPromise, reject);这一行中的重要性......感谢您耐心地重复我现在显而易见的事情。
  • 我越来越喜欢这种方法......因为它的纯粹性以及如何确保持续使用受控队列的容量(就如何入队 i>) 承诺的结构......我今天学到了很多。
  • 一个的答案。感谢@trincot 的知识分享
【解决方案2】:

在没有任何外部库的情况下,您可以这样做:

const ips = [
  /* List of mac address. */
];

useEffect(() => {
  const fetchData = async () => {
    const loadedData = [];
    
    // Iterate over the slices of array until all the ips have been processed.
    for (const sliceIps of sliceGenerator(ips)) {
      const gettingData = sliceIps.map(getDataFromIp);
      const sliceLoadedData = await axios.all(gettingData);
      loadedData = loadedData.concat(sliceLoadedData);
    }
    setData(loadedData);
  };
  fetchData();
}, []);

const getDataFromIp = (ip) =>
  axios.get("/ipdn/<MAC ADDRESS>", { timeout: 10000 });

// Generates a slice of an array, here the slice has a size of 50 max.
function* sliceGenerator(arr) {
  const sliceSize = 50;
  let i = 0;
  while (i < arr.length) {
    yield arr.splice(i, i + sliceSize);
    i += sliceSize;
  }
}

我在这里使用生成器function* sliceGenerator 来生成ips 数组的切片。这样你就可以批量处理它们 50 x 50。

我也在使用for (... of ...) 循环。非常方便,因为你可以在里面使用await关键字。

【讨论】:

  • 因为我await axios.all在循环中
  • 您的意思是使用 while 循环而不是 if 语句吗?
  • @PrathapReddy 这就是我想说的,他的函数只有一个产量,所以它只会给出第一个拼接。他应该在生成器函数中使用循环。他的函数将给出一个数组,其中包含一个长度为 50 的嵌套数组。
  • @RameshReddy,我的错。没观察到。他应该使用循环而不是if
  • 好点我没明白你的意思。我更新了我的代码
【解决方案3】:

如果您在使用外部库时没有任何问题,可以使用es6-promise-pool 来管理并发请求,如下所示

import PromisePool from 'es6-promise-pool';


// macs - Array of mac addresses
useEffect(() => {
  const fetchData = () => {
    const results = [];
    const generatePromises = function*() {
      for (let count = 0; count < macs.length; count++) {
        yield axios.get(`/ipdn/${macs[count]}`, ...);
      }
    }
    const promiseIterator = generatePromises();
    // Create a pool with 10 concurrent requests max
    const pool = new PromisePool(
      promiseIterator,
      10 // Configurable
    );
    // To listen to result
    pool.addEventListener('fulfilled', function (event) {
      console.log('Fulfilled: ' + event.data.result);
      results.push(event.data.result);
    });
    // Start the pool
    pool.start().then(function () {
      setData(results);
      console.log('Complete');
    });
  };
  fetchData();
}, []);

【讨论】:

  • 谢谢普拉塔普。您能否添加一些代码以查看如何将其与 useEffect 结合使用。
  • useEffect 中移动逻辑并捕获结果。您可以将它移动到一个数组并在池执行完成后使用setData 设置它。 (未添加错误处理)
  • 感谢大家的出色解决方案。 Prathap 我会投票给你的答案,因为我正在使用这个。
  • 尝试考虑@Bergi 评论(来自客户的 7000 条请求),以防有什么方法可以说服你的老板。 (考虑到缓慢/不可靠的互联网连接、移动网络等......)。快乐编码?
  • 如果没有使用async,为什么还要使用async?
【解决方案4】:

我对@9​​87654321@不熟悉,那么我提供一种使用Promise.all的方法。我的想法是将输入数组拆分为每块50个地址,然后一一解决

function App() {
  const [data, setData] = useState([]);

  useEffect(() => {
    // helper function, split array to chunked array
    const splitToChunks = (items, chunkSize = 50) => {
      const result = [];
      for (let i = 0; i < items.length; i += chunkSize) {
        result.push(items.slice(i, i + chunkSize));
      }
      return result;
    }
    
    const fetchData = async () => {
      const result = []; // init value
      const macAddresses = []; // array of mac addresses - your mac addresses
    
      const chunkedArray = splitToChunks(macAddresses); // return array of array [[...50 mac adds], [], []]
    
      for (const macs of chunkedArray) { // now macs is [...50 mac adds]
        const promises = macs.map((mac) => {
          return axios.get(`/ipdn/${mac}`, { timeout: 10000 });
        });
        // now promises is array contains 50 Promises
        const response = await Promise.all(promises); // wait until finish 50 requests
        result.push(...response); // copy response to result, and continue for next block
      }
    
      setData(result);
    };
    fetchData();
  }, []);
}

【讨论】:

    猜你喜欢
    • 2017-03-12
    • 2019-06-02
    • 2021-08-21
    • 2023-01-12
    • 1970-01-01
    • 2012-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多