在 Node.js 中执行大量 http 请求的最佳方式是什么?

What's the optimal way to perform a large quantity of http requests in Node.js?

假设一家商店有 500 种产品,每种产品的 ID 从 0 到 500,每种产品的数据都存储在位于 URL 下的 JSON 文件中(例如 myshop.com/1.json...2.json 等)。

使用 Node.js 脚本,我想下载所有这些 JSON 文件并将它们存储在本地。我可以连续做:

const totalProductsCount = 500;
try {
  let currentItem = 1;
  while (currentItem < (totalProductsCount + 1)) {
    const product = await axios.get(`https://myshop.com/${currentItem}.json`);
    fs.writeFileSync(`./product-${currentItem}.json`, JSON.stringify(product.data, null, 2));
    currentItem++;
  }
} catch (e) {
  return;
}

哪个有效。但是,我想快速下载这些文件,非常快。所以我试图将我所有的请求分成几组,并让这些组并行。我有以下内容:

  const _ = require('lodash');
  const fs = require('fs');
  const axios = require('axios');

  const getChunk = async (chunk, index) => {
    // The counter here is used for logging purposes only
    let currentItem = 1;
    try {
      // Iterate through the items 1-50 
      await chunk.reduce(async (promise, productId) => {
        await promise;
        const product = await axios.get(`https://myshop.com/${productId}`);
        if (product && product.data) {
          console.log('Got product', currentItem, 'from chunk', index);
          fs.writeFileSync(`./product-${productId}.json`, JSON.stringify(product.data, null, 2));
        }
        currentItem++;
      }, Promise.resolve());
    } catch (e) {
      throw e;
    }
  }

  const getProducts = async () => {
    const totalProductsCount = 500;
    // Create an array of 500 elements => [1, 2, 3, 4, ..., 499, 500]
    const productIds = Array.from({ length: totalProductsCount }, (_, i) => i + 1);
    // Using lodash, I am chunking that array into 10 groups of 50 each
    const chunkBy = Math.ceil(productIds.length / 10);
    const chunked = _.chunk(productIds, chunkBy);
    // Run the `getChunkProducts` on each of the chunks in parallel
    const products = await Promise.all([
      ...chunked.map((chunk, index) => getChunk(chunk, index))
    ])
    // If the items are to be returned here, it should be with a single-level array
    return _.flatten(products);
  };

  (async () => {
    const products = await getProducts();
  })()

这似乎大部分时间都有效,尤其是当我在较少数量的项目上使用时。但是,有一个我无法解释的行为,即当我请求大量物品时脚本挂起。

实现 this/best-practice 并能够捕获任何挂起或可能尚未下载的文件的最佳方法是什么(因为我的想法是,我可以通过分块下载任何我能下载的东西- action,然后取回所有下载失败的产品id数组,依次使用第一种方式下载)。

您正在异步操作中同步写入文件!更改 writeFileSync 以使用异步版本。这应该是一个立竿见影的改进。作为额外的性能增强,如果您希望将结果直接写入文件,您最好使用不解析响应的代码路径。看起来您可以在请求配置中使用 responseType: 'stream' 来完成此操作。这将防止在将响应写入文件之前将其解析为 JS 对象的开销。

听起来您可能还想将 HTTP 请求的超时调整到较低的水平,以确定它是否应该在几秒钟后失败,而不是等待您认为应该失败的请求。如果您参考文档,请求配置上有一个参数,您可以将其缩短到几秒钟。 https://axios-http.com/docs/req_config