在 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
假设一家商店有 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