在 node.js 中一次遍历一个包含 50 个项目的数组
Posted
技术标签:
【中文标题】在 node.js 中一次遍历一个包含 50 个项目的数组【英文标题】:Iterate through an array in blocks of 50 items at a time in node.js 【发布时间】:2018-03-19 19:54:36 【问题描述】:我是 node.js 的新手,目前正在尝试编写数组迭代代码。我有一个包含 1,000 个项目的数组 - 由于服务器负载问题,我想一次迭代 50 个项目。
我目前使用如下所示的 forEach 循环(我希望将其转换为上述块迭代)
//result is the array of 1000 items
result.forEach(function (item)
//Do some data parsing
//And upload data to server
);
任何帮助将不胜感激!
更新(响应回复)
async function uploadData(dataArray)
try
const chunks = chunkArray(dataArray, 50);
for (const chunk of chunks)
await uploadDataChunk(chunk);
catch (error)
console.log(error)
// Catch en error here
function uploadDataChunk(chunk)
return Promise.all(
chunk.map((item) =>
return new Promise((resolve, reject) =>
//upload code
)
)
)
【问题讨论】:
这听起来像XY problem 我假设您在循环中处理的任务是异步执行的。因此主要问题不是循环,而是异步任务立即返回,因此同时执行了 1000 个异步任务,对吧? @charlietfl 是和否,我知道我们的数据库可以处理的每秒写入请求数,blocks 功能可以让我保持在这些限制内 @Robert 这正是难题,我要求上传是异步的,但要求它限于我们数据库的写入限制能力 【参考方案1】:您应该首先将您的数组拆分为 50 个块。然后您需要一个一个地发出请求,而不是一次。 Promise 可用于此目的。
考虑这个实现:
function parseData() // returns an array of 1000 items
async function uploadData(dataArray)
try
const chunks = chunkArray(dataArray, 50);
for(const chunk of chunks)
await uploadDataChunk(chunk);
catch(error)
// Catch an error here
function uploadDataChunk(chunk)
// return a promise of chunk uploading result
const dataArray = parseData();
uploadData(dataArray);
使用 async/await 将在底层使用 Promise,因此await
将等待当前块上传,然后才会上传下一个块(如果没有发生错误)。
这里是我对 chunkArray 函数实现的建议:
function chunkArray(array, chunkSize)
return Array.from(
length: Math.ceil(array.length / chunkSize) ,
(_, index) => array.slice(index * chunkSize, (index + 1) * chunkSize)
);
注意:此代码使用 ES6 特性,因此最好使用 babel / TypeScript。
更新
如果您创建多个异步数据库连接,只需使用一些数据库池工具。
更新 2
如果你想异步更新所有的chunk,当chunk上传后开始上传另一个,你可以这样:
function uploadDataChunk(chunk)
return Promise.all(
chunk.map(uploadItemToGoogleCloud) // uploadItemToGoogleCloud should return a promise
);
【讨论】:
我从 50 个项目块以 forEach 格式运行我的数据库上传,一旦 forEach 完成对块的迭代,我如何返回一个承诺?非常感谢尤里! 你的意思是uploadDataChunk
里面的forEach?您是否单独上传每个项目?你的更新函数返回什么?是异步的吗?
是的,我使用 Google Cloud Firestore,它可以在每次上传文件时返回 Promise。但它不能以这种格式进行批量写入。我必须在 forEach 块中分别上传每个项目,并且由于速度原因以异步方式。计数器可以工作,但我不确定如何使用“等待”行实现完成以及触发下一个块上传所需的承诺
如果您想异步上传当前块中的所有 50 个项目,请查看我的更新答案(更新 2)。如果要同步上传每个项目,则不要分块。
我已经尝试了您的新代码,并且第一个块上传完美 - 但是该功能随后完成并且不会继续到下一个块。我已经发布了我的代码作为我的问题的更新。有任何想法吗?感谢您的帮助!【参考方案2】:
您可以按如下所需的块大小对数组进行分块;
function chunkArray(a,s) // a: array to chunk, s: size of chunks
return Array.from(length: Math.ceil(a.length / s))
.map((_,i) => Array.from(length: s)
.map((_,j) => a[i*s+j]));
var arr = Array(53).fill().map((_,i) => i); // test array of 53 items
console.log(chunkArray(arr,5)) // chunks of 5 items.
.as-console-wrapper
max-height: 100% ! important;
【讨论】:
您的代码运行良好,但另一个答案通过数据库上传和承诺提供了一些额外的调试。尽管如此,还是投了赞成票! @Hendies 谢谢...但我在这里展示的只是分块基础架构。然后你所要做的就是Promise.all()
块和Promise.all()
主数组。基本上就是这么酷..!
添加一个过滤器会很好——在示例代码中,最后一个数组中有 2 个undefined
。【参考方案3】:
有一个曾经非常流行的库:async.js(不要与 async
关键字混淆)。我仍然认为这有时是更清洁的方法,尽管这些天使用async/await
我倾向于在for
循环中手动完成。
异步库实现了许多异步流控制设计模式。对于这种情况,您可以使用eachLimit
:
const eachLimit = require('async/eachLimit');
eachLimit(result, 50,
function (item)
// do your forEach stuff here
,
function (err)
// this will be called when everything is completed
);
或者,如果您愿意,您可以使用 promisified 版本,以便您可以await
循环:
const eachLimit = require('async/eachLimit');
async function processResult (result)
// ...
try
await eachLimit(result, 50, function (item)
// do your forEach stuff here
);
catch (err)
// handle thrown errors
在这种特定情况下,手动批处理操作并使用await
在批处理之间暂停非常容易,但async.js
库包含一组丰富的函数,这些函数很有用。即使使用async/await
,其中一些仍然很难做到,例如whilst
(异步while
)、retry
、forever
等(参见文档:https://caolan.github.io/async/v3/docs.html)
【讨论】:
以上是关于在 node.js 中一次遍历一个包含 50 个项目的数组的主要内容,如果未能解决你的问题,请参考以下文章